Search code examples
goamazon-kms

Create KMS key policy in Go


I'm trying to create a KMS key using the AWS SDK v2 function call:

conn := kms.NewFromConfig(cfg)

input := kms.CreateKeyInput{
    KeySpec:     types.KeySpecEccNistP521,
    KeyUsage:    types.KeyUsageTypeSignVerify,
    MultiRegion: aws.Bool(true),
    Policy:      aws.String("")
}

output, err := conn.CreateKey(ctx, &input)

The problem I'm having is that I'm not sure how to generate the policy for the key. I assume I could create JSON for an IAM policy document, but I don't find the prospect of generating that myself to be particularly inviting. Is there a package or library that I can use to generate this document?


Solution

  • I ended up creating my own policy structs:

    // Policy describes a policy document that can be used to configure permissions in IAM
    type Policy struct {
        Version    string       `json:"Version"`
        ID         string       `json:"Id"`
        Statements []*Statement `json:"Statement"`
    }
    
    // Statement describes a set of permissions that define what resources and users should have access
    // to the resources described therein
    type Statement struct {
        ID            string     `json:"Sid"`
        Effect        Effect     `json:"Effect"`
        PrincipalArns Principals `json:"Principal"`
        ActionArns    Actions    `json:"Action"`
        ResourceArns  Resources  `json:"Resource"`
    }
    
    // Principals describes a list of principals associated with a policy statement
    type Principals []string
    
    // MarhsalJSON converts a Principals collection to JSON
    func (p Principals) MarshalJSON() ([]byte, error) {
    
        // First, get the inner string from the list of principals
        var inner string
        if len(p) > 1 {
            inner = marshal(p...)
        } else if len(p) == 1 {
            inner = strings.Quote(p[0], "\"")
        } else {
            return nil, fmt.Errorf("Principal must contain at least one element")
        }
    
        // Next, create the principal block and return it
        return []byte(fmt.Sprintf("{\"AWS\": %s}", inner)), nil
    }
    
    // Actions describes a list of actions that may or may not be taken by principals with regard to the
    // resources described in a policy statement
    type Actions []Action
    
    // MarshalJSON converts an Actions collection to JSON
    func (a Actions) MarshalJSON() ([]byte, error) {
    
        // First, get the inner string from the list of actions
        var inner string
        if len(a) > 1 {
            inner = marshal(a...)
        } else if len(a) == 1 {
            inner = strings.Quote(a[0], "\"")
        } else {
            return nil, fmt.Errorf("Action must contain at least one element")
        }
    
        // Next, create the action block and return it
        return []byte(inner), nil
    }
    
    // Resources describes a list of resources effected by the policy statement
    type Resources []string
    
    // MarshalJSON converts a Resources collection to JSON
    func (r Resources) MarshalJSON() ([]byte, error) {
    
        // First, get the inner string from the list of actions
        var inner string
        if len(r) > 1 {
            inner = marshal(r...)
        } else if len(r) == 1 {
            inner = strings.Quote(r[0], "\"")
        } else {
            return nil, fmt.Errorf("Resource must contain at least one element")
        }
    
        // Next, create the action block and return it
        return []byte(inner), nil
    }
    
    // Helper function that converts a list of items to a JSON-string
    func marshal[S ~string](items ...S) string {
        return "[" + strings.ModifyAndJoin(func(item string) string { return strings.Quote(item, "\"") }, ",", items...) + "]"
    }
    
    // Effect describes the effect a policy statement will have upon the resource and for the actions described
    type Effect string
    
    var (
    
        // Allow to grant access of the resource and actions to the principals described in the policy statement
        Allow = Effect("Allow")
    
        // Deny to deny access of the resource and actions from the principals described in the policy statement
        Deny = Effect("Deny")
    )
    
    // Action describes a valid operation that may be made against a particular AWS resource
    type Action string
    
    // Describes the various action types available to AWS
    var (
        CancelKeyDeletion                   = Action("kms:CancelKeyDeletion")
        ConnectCustomKeyStore               = Action("kms:ConnectCustomKeyStore")
        CreateAlias                         = Action("kms:CreateAlias")
        CreateCustomKeyStore                = Action("kms:CreateCustomKeyStore")
        CreateGrant                         = Action("kms:CreateGrant")
        CreateKey                           = Action("kms:CreateKey")
        Decrypt                             = Action("kms:Decrypt")
        DeleteAlias                         = Action("kms:DeleteAlias")
        DeleteCustomKeyStore                = Action("kms:DeleteCustomKeyStore")
        DeleteImportedKeyMaterial           = Action("kms:DeleteImportedKeyMaterial")
        DescribeCustomKeyStores             = Action("kms:DescribeCustomKeyStores")
        DescribeKey                         = Action("kms:DescribeKey")
        DisableKey                          = Action("kms:DisableKey")
        DisableKeyRotation                  = Action("kms:DisableKeyRotation")
        DisconnectCustomKeyStore            = Action("kms:DisconnectCustomKeyStore")
        EnableKey                           = Action("kms:EnableKey")
        EnableKeyRotation                   = Action("kms:EnableKeyRotation")
        Encrypt                             = Action("kms:Encrypt")
        GenerateDataKey                     = Action("kms:GenerateDataKey")
        GenerateDataKeyPair                 = Action("kms:GenerateDataKeyPair")
        GenerateDataKeyPairWithoutPlaintext = Action("kms:GenerateDataKeyPairWithoutPlaintext")
        GenerateDataKeyWithoutPlaintext     = Action("kms:GenerateDataKeyWithoutPlaintext")
        GenerateMac                         = Action("kms:GenerateMac")
        GenerateRandom                      = Action("kms:GenerateRandom")
        GetKeyPolicy                        = Action("kms:GetKeyPolicy")
        GetKeyRotationStatus                = Action("kms:GetKeyRotationStatus")
        GetParametersForImport              = Action("kms:GetParametersForImport")
        GetPublicKey                        = Action("kms:GetPublicKey")
        ImportKeyMaterial                   = Action("kms:ImportKeyMaterial")
        ListAliases                         = Action("kms:ListAliases")
        ListGrants                          = Action("kms:ListGrants")
        ListKeyPolicies                     = Action("kms:ListKeyPolicies")
        ListKeys                            = Action("kms:ListKeys")
        ListResourceTags                    = Action("kms:ListResourceTags")
        ListRetirableGrants                 = Action("kms:ListRetirableGrants")
        PutKeyPolicy                        = Action("kms:PutKeyPolicy")
        ReEncryptFrom                       = Action("kms:ReEncryptFrom")
        ReEncryptTo                         = Action("kms:ReEncryptTo")
        ReplicateKey                        = Action("kms:ReplicateKey")
        RetireGrant                         = Action("kms:RetireGrant")
        RevokeGrant                         = Action("kms:RevokeGrant")
        ScheduleKeyDeletion                 = Action("kms:ScheduleKeyDeletion")
        Sign                                = Action("kms:Sign")
        TagResource                         = Action("kms:TagResource")
        UntagResource                       = Action("kms:UntagResource")
        UpdateAlias                         = Action("kms:UpdateAlias")
        UpdateCustomKeyStore                = Action("kms:UpdateCustomKeyStore")
        UpdateKeyDescription                = Action("kms:UpdateKeyDescription")
        UpdatePrimaryRegion                 = Action("kms:UpdatePrimaryRegion")
        Verify                              = Action("kms:Verify")
        VerifyMac                           = Action("kms:VerifyMac")
        KmsAll                              = Action("kms:*")
    )
    

    I can then use this in my code like so:

    conn := kms.NewFromConfig(cfg)
    
    policy := Policy {
        Version: "2012-10-17",
        ID:      "test-key",
        Statements: []*policy.Statement{
            {
                ID:            "test-failure",
                Effect:        policy.Allow,
                PrincipalArns: []string{"arn:aws:kms:eu-west-2:111122223333:root"},
                ActionArns:    policy.Actions{policy.KmsAll},
                ResourceArns:  []string{"*"},
            },
        },
    }
    
    pData, err := json.Marshal(policy)
    if err != nil {
       return err
    }
    
    input := kms.CreateKeyInput{
        KeySpec:     types.KeySpecEccNistP521,
        KeyUsage:    types.KeyUsageTypeSignVerify,
        MultiRegion: aws.Bool(true),
        Policy:      aws.String(string(pData)),
    }
    
    output, err := conn.CreateKey(ctx, &input)
    

    I added the code for this in an open-source package that can be found here so others can use it.