Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new credential provider for AWS credential files #4067

Merged
merged 2 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/content/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,19 @@ Please note that if you are using temporary IAM credentials (e.g. assumed IAM ro
| --- | --- | --- | --- |
| `services[_].credentials.s3_signing.environment_credentials` | `{}` | Yes | Enables AWS signing using environment variables to source the configuration and credentials |


##### Using Named Profile Credentials
If specifying `profile_credentials`, OPA will expect to find the `access key id`, `secret access key` and
`session token` from the [named profiles](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
stored in the [credentials](https://docs.aws.amazon.com/sdkref/latest/guide/file-format.html) file on disk. On each
request OPA will re-read the credentials from the file and use them for authentication.

| Field | Type | Required | Description |
| --- | --- | --- | --- |
| `services[_].credentials.s3_signing.profile_credentials.path` | `string` | No | The path to the shared credentials file. If empty, OPA will look for the `AWS_SHARED_CREDENTIALS_FILE` env variable. If the variable is not set, the path defaults to the current user's home directory. `~/.aws/credentials` (Linux & Mac) or `%USERPROFILE%\.aws\credentials` (Windows) |
| `services[_].credentials.s3_signing.profile_credentials.profile` | `string` | No | AWS Profile to extract credentials from the credentials file. If empty, OPA will look for the `AWS_PROFILE` env variable. If the variable is not set, the `default` profile will be used |
| `services[_].credentials.s3_signing.metadata_credentials.aws_region` | `string` | No | The AWS region to use for the AWS signing service credential method. If unset, the `AWS_REGION` environment variable must be set |

##### Using EC2 Metadata Credentials
If specifying `metadata_credentials`, OPA will use the AWS metadata services for [EC2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html)
or [ECS](https://docs.aws.amazon.com/AmazonECS/latest/userguide/task-iam-roles.html)
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ require (
github.com/foxcpp/go-mockdns v0.0.0-20210729171921-fb145fc6f897
github.com/fsnotify/fsnotify v1.5.1
github.com/ghodss/yaml v1.0.0
github.com/go-ini/ini v1.64.0
github.com/gobwas/glob v0.2.3
github.com/golang/glog v1.0.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-ini/ini v1.64.0 h1:73w/ADE+yoYjfu4BlI/LaEMe9Do1zOQ6qPt1du4uikI=
github.com/go-ini/ini v1.64.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
Expand Down
107 changes: 106 additions & 1 deletion plugins/rest/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,13 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"sort"
"strings"
"time"

"github.com/go-ini/ini"

"github.com/open-policy-agent/opa/logging"
)

Expand All @@ -46,6 +49,13 @@ const (
awsRegionEnvVar = "AWS_REGION"
awsRoleArnEnvVar = "AWS_ROLE_ARN"
awsWebIdentityTokenFileEnvVar = "AWS_WEB_IDENTITY_TOKEN_FILE"
awsCredentialsFileEnvVar = "AWS_SHARED_CREDENTIALS_FILE"
awsProfileEnvVar = "AWS_PROFILE"

// ref. https://docs.aws.amazon.com/sdkref/latest/guide/settings-global.html
accessKeyGlobalSetting = "aws_access_key_id"
secretKeyGlobalSetting = "aws_secret_access_key"
securityTokenGlobalSetting = "aws_session_token"
)

// Headers that may be mutated before reaching an aws service (eg by a proxy) should be added here to omit them from
Expand Down Expand Up @@ -89,7 +99,7 @@ func (cs *awsEnvironmentCredentialService) credentials() (awsCredentials, error)
if creds.RegionName == "" {
return creds, errors.New("no " + awsRegionEnvVar + " set in environment")
}
// SessionToken is required if using temporaty ENV credentials from assumed IAM role
// SessionToken is required if using temporary ENV credentials from assumed IAM role
// Missing SessionToken results with 403 s3 error.
creds.SessionToken = os.Getenv(sessionTokenEnvVar)
if creds.SessionToken == "" {
Expand All @@ -101,6 +111,101 @@ func (cs *awsEnvironmentCredentialService) credentials() (awsCredentials, error)
return creds, nil
}

// awsProfileCredentialService represents a credential provider for AWS that extracts credentials from the AWS
// credentials file
type awsProfileCredentialService struct {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting how the presence of comments makes go fmt ignore formatting of the struct. Did not know that from before :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How so? I think it always only aligned blocks, like

type x struct {
	a    string
	bbbb bool

	cccccccc int
	d        struct{}
}

We've got four blocks (one field each) here, so it looks a little wild 😄 


// Path to the credentials file.
//
// If empty will look for "AWS_SHARED_CREDENTIALS_FILE" env variable. If the
// env value is empty will default to current user's home directory.
// Linux/OSX: "$HOME/.aws/credentials"
// Windows: "%USERPROFILE%\.aws\credentials"
Path string `json:"path,omitempty"`

// AWS Profile to extract credentials from the credentials file. If empty
// will default to environment variable "AWS_PROFILE" or "default" if
// environment variable is also not set.
Profile string `json:"profile,omitempty"`

RegionName string `json:"aws_region"`

logger logging.Logger
}

func (cs *awsProfileCredentialService) credentials() (awsCredentials, error) {
var creds awsCredentials

filename, err := cs.path()
if err != nil {
return creds, err
}

cfg, err := ini.Load(filename)
if err != nil {
return creds, fmt.Errorf("failed to read credentials file: %v", err)
}

profile, err := cfg.GetSection(cs.profile())
if err != nil {
return creds, fmt.Errorf("failed to get profile: %v", err)
}

creds.AccessKey = profile.Key(accessKeyGlobalSetting).String()
if creds.AccessKey == "" {
return creds, fmt.Errorf("profile \"%v\" in credentials file %v does not contain \"%v\"", cs.Profile, cs.Path, accessKeyGlobalSetting)
}

creds.SecretKey = profile.Key(secretKeyGlobalSetting).String()
if creds.SecretKey == "" {
return creds, fmt.Errorf("profile \"%v\" in credentials file %v does not contain \"%v\"", cs.Profile, cs.Path, secretKeyGlobalSetting)
}

creds.SessionToken = profile.Key(securityTokenGlobalSetting).String() //default to empty string

if cs.RegionName == "" {
if cs.RegionName = os.Getenv(awsRegionEnvVar); cs.RegionName == "" {
return creds, errors.New("no " + awsRegionEnvVar + " set in environment or configuration")
}
}
creds.RegionName = cs.RegionName

return creds, nil
}

func (cs *awsProfileCredentialService) path() (string, error) {
if len(cs.Path) != 0 {
return cs.Path, nil
}

if cs.Path = os.Getenv(awsCredentialsFileEnvVar); len(cs.Path) != 0 {
return cs.Path, nil
}

homeDir, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("user home directory not found: %w", err)
}

cs.Path = filepath.Join(homeDir, ".aws", "credentials")

return cs.Path, nil
}

func (cs *awsProfileCredentialService) profile() string {
if cs.Profile != "" {
return cs.Profile
}

cs.Profile = os.Getenv(awsProfileEnvVar)

if cs.Profile == "" {
cs.Profile = "default"
}

return cs.Profile
}

// awsMetadataCredentialService represents an EC2 metadata service credential provider for AWS
type awsMetadataCredentialService struct {
RoleName string `json:"iam_role,omitempty"`
Expand Down
Loading