vault/builtin/logical/aws/iam_policies.go
hashicorp-copywrite[bot] 0b12cdcfd1
[COMPLIANCE] License changes (#22290)
* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Adding explicit MPL license for sub-package.

This directory and its subdirectories (packages) contain files licensed with the MPLv2 `LICENSE` file in this directory and are intentionally licensed separately from the BSL `LICENSE` file at the root of this repository.

* Updating the license from MPL to Business Source License.

Going forward, this project will be licensed under the Business Source License v1.1. Please see our blog post for more details at https://hashi.co/bsl-blog, FAQ at www.hashicorp.com/licensing-faq, and details of the license at www.hashicorp.com/bsl.

* add missing license headers

* Update copyright file headers to BUS-1.1

* Fix test that expected exact offset on hcl file

---------

Co-authored-by: hashicorp-copywrite[bot] <110428419+hashicorp-copywrite[bot]@users.noreply.github.com>
Co-authored-by: Sarah Thompson <sthompson@hashicorp.com>
Co-authored-by: Brian Kassouf <bkassouf@hashicorp.com>
2023-08-10 18:14:03 -07:00

145 lines
4.1 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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
}