open-vault/builtin/logical/aws/iam_policies.go
Mason Foster 09c6ff0623
aws: pass cancelable context with aws calls (#19365)
* auth/aws: use cancelable context with aws calls

* secrets/aws: use cancelable context with aws calls
2023-03-23 12:02:24 -05:00

145 lines
4.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package aws
import (
"context"
"encoding/json"
"fmt"
"net/url"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/aws/aws-sdk-go/service/iam/iamiface"
"github.com/hashicorp/vault/sdk/logical"
)
// PolicyDocument represents an IAM policy document
type PolicyDocument struct {
Version string `json:"Version"`
Statements StatementEntries `json:"Statement"`
}
// StatementEntries is a slice of statements that make up a PolicyDocument
type StatementEntries []interface{}
// UnmarshalJSON is defined here for StatementEntries because the Statement
// portion of an IAM Policy can either be a list or a single element, so if it's
// a single element this wraps it in a []interface{} so that it's easy to
// combine with other policy statements:
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_statement.html
func (se *StatementEntries) UnmarshalJSON(b []byte) error {
var out StatementEntries
var data interface{}
if err := json.Unmarshal(b, &data); err != nil {
return err
}
switch t := data.(type) {
case []interface{}:
out = t
case interface{}:
out = []interface{}{t}
default:
return fmt.Errorf("unsupported data type %T for StatementEntries", t)
}
*se = out
return nil
}
// getGroupPolicies takes a list of IAM Group names and returns a list of their
// inline policy documents, and a list of the attached managed policy ARNs
func (b *backend) getGroupPolicies(ctx context.Context, s logical.Storage, iamGroups []string) ([]string, []string, error) {
var groupPolicies []string
var groupPolicyARNs []string
var err error
var agp *iam.ListAttachedGroupPoliciesOutput
var inlinePolicies *iam.ListGroupPoliciesOutput
var inlinePolicyDoc *iam.GetGroupPolicyOutput
var iamClient iamiface.IAMAPI
// Return early if there are no groups, to avoid creating an IAM client
// needlessly
if len(iamGroups) == 0 {
return nil, nil, nil
}
iamClient, err = b.clientIAM(ctx, s)
if err != nil {
return nil, nil, err
}
for _, g := range iamGroups {
// Collect managed policy ARNs from the IAM Group
agp, err = iamClient.ListAttachedGroupPoliciesWithContext(ctx, &iam.ListAttachedGroupPoliciesInput{
GroupName: aws.String(g),
})
if err != nil {
return nil, nil, err
}
for _, p := range agp.AttachedPolicies {
groupPolicyARNs = append(groupPolicyARNs, *p.PolicyArn)
}
// Collect inline policy names from the IAM Group
inlinePolicies, err = iamClient.ListGroupPoliciesWithContext(ctx, &iam.ListGroupPoliciesInput{
GroupName: aws.String(g),
})
if err != nil {
return nil, nil, err
}
for _, iP := range inlinePolicies.PolicyNames {
inlinePolicyDoc, err = iamClient.GetGroupPolicyWithContext(ctx, &iam.GetGroupPolicyInput{
GroupName: &g,
PolicyName: iP,
})
if err != nil {
return nil, nil, err
}
if inlinePolicyDoc != nil && inlinePolicyDoc.PolicyDocument != nil {
var policyStr string
if policyStr, err = url.QueryUnescape(*inlinePolicyDoc.PolicyDocument); err != nil {
return nil, nil, err
}
groupPolicies = append(groupPolicies, policyStr)
}
}
}
return groupPolicies, groupPolicyARNs, nil
}
// combinePolicyDocuments takes policy strings as input, and combines them into
// a single policy document string
func combinePolicyDocuments(policies ...string) (string, error) {
var policy string
var err error
var policyBytes []byte
newPolicy := PolicyDocument{
// 2012-10-17 is the current version of the AWS policy language:
// https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_version.html
Version: "2012-10-17",
}
newPolicy.Statements = make(StatementEntries, 0, len(policies))
for _, p := range policies {
if len(p) == 0 {
continue
}
var tmpDoc PolicyDocument
err = json.Unmarshal([]byte(p), &tmpDoc)
if err != nil {
return "", err
}
newPolicy.Statements = append(newPolicy.Statements, tmpDoc.Statements...)
}
policyBytes, err = json.Marshal(&newPolicy)
if err != nil {
return "", err
}
policy = string(policyBytes)
return policy, nil
}