open-consul/agent/consul/authmethod/awsauth/aws.go

193 lines
5.8 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package awsauth
import (
"context"
"fmt"
iamauth "github.com/hashicorp/consul-awsauth"
"github.com/hashicorp/consul/agent/consul/authmethod"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/go-hclog"
)
const (
authMethodType string = "aws-iam"
IAMServerIDHeaderName string = "X-Consul-IAM-ServerID"
GetEntityMethodHeader string = "X-Consul-IAM-GetEntity-Method"
GetEntityURLHeader string = "X-Consul-IAM-GetEntity-URL"
GetEntityHeadersHeader string = "X-Consul-IAM-GetEntity-Headers"
GetEntityBodyHeader string = "X-Consul-IAM-GetEntity-Body"
)
func init() {
// register this as an available auth method type
authmethod.Register(authMethodType, func(logger hclog.Logger, method *structs.ACLAuthMethod) (authmethod.Validator, error) {
v, err := NewValidator(logger, method)
if err != nil {
return nil, err
}
return v, nil
})
}
type Config struct {
// BoundIAMPrincipalARNs are the trusted AWS IAM principal ARNs that are permitted
// to login to the auth method. These can be the exact ARNs or wildcards. Wildcards
// are only supported if EnableIAMEntityDetails is true.
BoundIAMPrincipalARNs []string `json:",omitempty"`
// EnableIAMEntityDetails will fetch the IAM User or IAM Role details to include
// in binding rules. Required if wildcard principal ARNs are used.
EnableIAMEntityDetails bool `json:",omitempty"`
// IAMEntityTags are the specific IAM User or IAM Role tags to include as selectable
// fields in the binding rule attributes. Requires EnableIAMEntityDetails = true.
IAMEntityTags []string `json:",omitempty"`
// ServerIDHeaderValue adds a X-Consul-IAM-ServerID header to each AWS API request.
// This helps protect against replay attacks.
ServerIDHeaderValue string `json:",omitempty"`
// MaxRetries is the maximum number of retries on AWS API requests for recoverable errors.
MaxRetries int `json:",omitempty"`
// IAMEndpoint is the AWS IAM endpoint where iam:GetRole or iam:GetUser requests will be sent.
// Note that the Host header in a signed request cannot be changed.
IAMEndpoint string `json:",omitempty"`
// STSEndpoint is the AWS STS endpoint where sts:GetCallerIdentity requests will be sent.
// Note that the Host header in a signed request cannot be changed.
STSEndpoint string `json:",omitempty"`
// AllowedSTSHeaderValues is a list of additional allowed headers on the sts:GetCallerIdentity
// request in the bearer token. A default list of necessary headers is allowed in any case.
AllowedSTSHeaderValues []string `json:",omitempty"`
}
func (c *Config) convertForLibrary() *iamauth.Config {
return &iamauth.Config{
BoundIAMPrincipalARNs: c.BoundIAMPrincipalARNs,
EnableIAMEntityDetails: c.EnableIAMEntityDetails,
IAMEntityTags: c.IAMEntityTags,
ServerIDHeaderValue: c.ServerIDHeaderValue,
MaxRetries: c.MaxRetries,
IAMEndpoint: c.IAMEndpoint,
STSEndpoint: c.STSEndpoint,
AllowedSTSHeaderValues: c.AllowedSTSHeaderValues,
ServerIDHeaderName: IAMServerIDHeaderName,
GetEntityMethodHeader: GetEntityMethodHeader,
GetEntityURLHeader: GetEntityURLHeader,
GetEntityHeadersHeader: GetEntityHeadersHeader,
GetEntityBodyHeader: GetEntityBodyHeader,
}
}
type Validator struct {
name string
config *iamauth.Config
logger hclog.Logger
auth *iamauth.Authenticator
}
func NewValidator(logger hclog.Logger, method *structs.ACLAuthMethod) (*Validator, error) {
if method.Type != authMethodType {
return nil, fmt.Errorf("%q is not an AWS IAM auth method", method.Name)
}
var config Config
if err := authmethod.ParseConfig(method.Config, &config); err != nil {
return nil, err
}
iamConfig := config.convertForLibrary()
auth, err := iamauth.NewAuthenticator(iamConfig, logger)
if err != nil {
return nil, err
}
return &Validator{
name: method.Name,
config: iamConfig,
logger: logger,
auth: auth,
}, nil
}
// Name implements authmethod.Validator.
func (v *Validator) Name() string { return v.name }
// Stop implements authmethod.Validator.
func (v *Validator) Stop() {}
// ValidateLogin implements authmethod.Validator.
func (v *Validator) ValidateLogin(ctx context.Context, loginToken string) (*authmethod.Identity, error) {
details, err := v.auth.ValidateLogin(ctx, loginToken)
if err != nil {
return nil, err
}
vars := map[string]string{
"entity_name": details.EntityName,
"entity_id": details.EntityId,
"account_id": details.AccountId,
}
fields := &awsSelectableFields{
EntityName: details.EntityName,
EntityId: details.EntityId,
AccountId: details.AccountId,
}
if v.config.EnableIAMEntityDetails {
vars["entity_path"] = details.EntityPath
fields.EntityPath = details.EntityPath
fields.EntityTags = map[string]string{}
for _, tag := range v.config.IAMEntityTags {
vars["entity_tags."+tag] = details.EntityTags[tag]
fields.EntityTags[tag] = details.EntityTags[tag]
}
}
result := &authmethod.Identity{
SelectableFields: fields,
ProjectedVars: vars,
EnterpriseMeta: nil,
}
return result, nil
}
func (v *Validator) NewIdentity() *authmethod.Identity {
fields := &awsSelectableFields{
EntityTags: map[string]string{},
}
vars := map[string]string{
"entity_name": "",
"entity_id": "",
"account_id": "",
}
if v.config.EnableIAMEntityDetails {
vars["entity_path"] = ""
for _, tag := range v.config.IAMEntityTags {
vars["entity_tags."+tag] = ""
fields.EntityTags[tag] = ""
}
}
return &authmethod.Identity{
SelectableFields: fields,
ProjectedVars: vars,
}
}
type awsSelectableFields struct {
EntityName string `bexpr:"entity_name"`
EntityId string `bexpr:"entity_id"`
AccountId string `bexpr:"account_id"`
EntityPath string `bexpr:"entity_path"`
EntityTags map[string]string `bexpr:"entity_tags"`
}