Skip to content

Commit

Permalink
Loop up RoleMapping with UserId in dynamocfile mode
Browse files Browse the repository at this point in the history
  • Loading branch information
nnmin-aws committed Feb 20, 2023
1 parent b6f39da commit 42d9d9b
Show file tree
Hide file tree
Showing 12 changed files with 196 additions and 56 deletions.
1 change: 1 addition & 0 deletions cmd/aws-iam-authenticator/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ func getConfig() (config.Config, error) {
EC2DescribeInstancesBurst: viper.GetInt("server.ec2DescribeInstancesBurst"),
ScrubbedAWSAccounts: viper.GetStringSlice("server.scrubbedAccounts"),
DynamicFilePath: viper.GetString("server.dynamicfilepath"),
DynamicFileUserIDStrict: viper.GetBool("server.dynamicfileUserIDStrict"),
}
if err := viper.UnmarshalKey("server.mapRoles", &cfg.RoleMappings); err != nil {
return cfg, fmt.Errorf("invalid server role mappings: %v", err)
Expand Down
3 changes: 2 additions & 1 deletion hack/dev/access-entries.template
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
"username": "kubernetes-admin",
"groups": [
"system:masters"
]
],
"userid": "{{USER_ID}}"
}
]
}
Expand Down
4 changes: 3 additions & 1 deletion hack/e2e-dynamicfile.sh
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ function e2e_dynamicfile(){
echo "can't assume-role: "${AWS_TEST_ROLE}
exit 1
fi

USERID=$(aws sts get-caller-identity|jq -r '.UserId'|cut -d: -f1)
echo "userid: " $USERID
#run kubectl cmd without adding the role into access entry
if [ -f ${access_entry_json} ]
then
Expand All @@ -123,6 +124,7 @@ function e2e_dynamicfile(){

sed -e "s|{{AWS_ACCOUNT}}|${AWS_ACCOUNT}|g" \
-e "s|{{AWS_TEST_ROLE}}|${AWS_TEST_ROLE}|g" \
-e "s|{{USER_ID}}|${USERID}|g" \
"${access_entry_template}" > "${access_entry_tmp}"
mv "${access_entry_tmp}" "${access_entry_json}"
#sleep 10 seconds to make access entry effective
Expand Down
35 changes: 33 additions & 2 deletions pkg/config/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ type RoleMapping struct {

// Groups is a list of Kubernetes groups this role will authenticate
// as (e.g., `system:masters`). Each group name can include placeholders.
Groups []string `json:"groups"`
Groups []string `json:"groups" yaml:"groups"`

// UserId is the AWS PrincipalId of the role. (e.g., "ABCXSOTJDDV").
UserId string `json:"userid,omitempty" yaml:"userid,omitempty"`
}

// UserMapping is a static mapping of a single AWS User ARN to a
Expand All @@ -65,7 +68,33 @@ type UserMapping struct {
Username string `json:"username"`

// Groups is a list of Kubernetes groups this role will authenticate as (e.g., `system:masters`)
Groups []string `json:"groups"`
Groups []string `json:"groups" yaml:"groups"`

// UserId is the AWS PrincipalId of the user. (e.g., "ABCXSOTJDDV").
UserId string `json:"userid,omitempty" yaml:"userid,omitempty"`
}

// SSOARNMatcher contains fields used to match Role ARNs that
// are generated for AWS SSO sessions. These SSO Role ARNs
// follow this pattern:
//
// arn:aws:iam::<ACCOUNT_ID>:role/aws-reserved/sso.amazonaws.com/<SSO_REGION>/AWSReservedSSO_<SSO_PermissionSetName>_<RANDOM_STRING>
//
// These ARNs are canonicalized to look like:
//
// arn:aws:iam::<ACCOUNT_ID>:role/AWSReservedSSO_<SSO_PermissionSetName>_<RANDOM_STRING>
//
// This struct enables aws-iam-authenticator to match SSO generated Role ARNs with
// handling for their random string suffixes.
type SSOARNMatcher struct {
// PermissionSetName is the name of the SSO Permission Set that will be found
// after the "AWSReservedSSO_" string in the Role ARN.
// See: https://docs.aws.amazon.com/singlesignon/latest/userguide/permissionsets.html
PermissionSetName string `json:"permissionSetName" yaml:"permissionSetName"`
// AccountID is the AWS Account ID to match in the Role ARN
AccountID string `json:"accountID" yaml:"accountID"`
// Partition is the AWS partition to match in the Role ARN. Defaults to "aws"
Partition string `json:"partition,omitempty" yaml:"partition,omitempty"`
}

// Config specifies the configuration for a aws-iam-authenticator server
Expand Down Expand Up @@ -144,4 +173,6 @@ type Config struct {
EC2DescribeInstancesBurst int
//Dynamic File Path for DynamicFile BackendMode
DynamicFilePath string
//use UserId for mapping, IdentityArn is not used any more when DynamicFileUserIDStrict=true
DynamicFileUserIDStrict bool
}
5 changes: 3 additions & 2 deletions pkg/mapper/configmap/mapper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package configmap

import (
"sigs.k8s.io/aws-iam-authenticator/pkg/token"
"strings"

"sigs.k8s.io/aws-iam-authenticator/pkg/config"
Expand Down Expand Up @@ -30,8 +31,8 @@ func (m *ConfigMapMapper) Start(stopCh <-chan struct{}) error {
return nil
}

func (m *ConfigMapMapper) Map(canonicalARN string) (*config.IdentityMapping, error) {
canonicalARN = strings.ToLower(canonicalARN)
func (m *ConfigMapMapper) Map(identity *token.Identity) (*config.IdentityMapping, error) {
canonicalARN := strings.ToLower(identity.CanonicalARN)

rm, err := m.RoleMapping(canonicalARN)
// TODO: Check for non Role/UserNotFound errors
Expand Down
5 changes: 3 additions & 2 deletions pkg/mapper/crd/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package crd

import (
"fmt"
"sigs.k8s.io/aws-iam-authenticator/pkg/token"
"strings"
"time"

Expand Down Expand Up @@ -86,8 +87,8 @@ func (m *CRDMapper) Start(stopCh <-chan struct{}) error {
return nil
}

func (m *CRDMapper) Map(canonicalARN string) (*config.IdentityMapping, error) {
canonicalARN = strings.ToLower(canonicalARN)
func (m *CRDMapper) Map(identity *token.Identity) (*config.IdentityMapping, error) {
canonicalARN := strings.ToLower(identity.CanonicalARN)

var iamidentity *iamauthenticatorv1alpha1.IAMIdentityMapping
var ok bool
Expand Down
55 changes: 35 additions & 20 deletions pkg/mapper/dynamicfile/dynamicfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,25 @@ import (
"encoding/json"
"errors"
"fmt"
"os"
"strings"
"sync"
"time"

"github.com/fsnotify/fsnotify"
"github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/wait"
"os"
"sigs.k8s.io/aws-iam-authenticator/pkg/arn"
"sigs.k8s.io/aws-iam-authenticator/pkg/config"
"strings"
"sync"
"time"
)

type DynamicFileMapStore struct {
mutex sync.RWMutex
users map[string]config.UserMapping
roles map[string]config.RoleMapping
// Used as set.
awsAccounts map[string]interface{}
filename string
awsAccounts map[string]interface{}
filename string
userIDStrict bool
}

type DynamicFileData struct {
Expand Down Expand Up @@ -66,7 +66,7 @@ func (m *DynamicFileMapStore) loadDynamicFile() error {
}
logrus.Infof("LoadDynamicFile: %v is available. loading", m.filename)
// load the initial file content into memory
userMappings, roleMappings, awsAccounts, err := ParseMap(m.filename)
userMappings, roleMappings, awsAccounts, err := ParseMap(m)
if err != nil {
logrus.Errorf("LoadDynamicFile: There was an error parsing the dynamic file: %+v. Map is not updated. Please correct dynamic file", err)
return err
Expand All @@ -76,9 +76,10 @@ func (m *DynamicFileMapStore) loadDynamicFile() error {
return nil
}

func NewDynamicFileMapStore(filename string) (*DynamicFileMapStore, error) {
func NewDynamicFileMapStore(cfg config.Config) (*DynamicFileMapStore, error) {
ms := DynamicFileMapStore{}
ms.filename = filename
ms.filename = cfg.DynamicFilePath
ms.userIDStrict = cfg.DynamicFileUserIDStrict
return &ms, nil
}

Expand Down Expand Up @@ -127,11 +128,11 @@ func (m *DynamicFileMapStore) startLoadDynamicFile(stopCh <-chan struct{}) {
}, time.Second, stopCh)
}

func ParseMap(filename string) (userMappings []config.UserMapping, roleMappings []config.RoleMapping, awsAccounts []string, err error) {
func ParseMap(m *DynamicFileMapStore) (userMappings []config.UserMapping, roleMappings []config.RoleMapping, awsAccounts []string, err error) {
errs := make([]error, 0)
userMappings = make([]config.UserMapping, 0)
roleMappings = make([]config.RoleMapping, 0)

filename := m.filename
dynamicContent, err := os.ReadFile(filename)
if err != nil {
logrus.Errorf("ParseMap: could not read from dynamic file")
Expand All @@ -149,16 +150,24 @@ func ParseMap(filename string) (userMappings []config.UserMapping, roleMappings
}

for _, userMapping := range dynamicFileData.UserMappings {
if userMapping.UserARN == "" {
errs = append(errs, fmt.Errorf("Value for userarn must be supplied"))
key := userMapping.UserARN
if m.userIDStrict {
key = userMapping.UserId
}
if key == "" {
errs = append(errs, fmt.Errorf("Value for userarn or userid(if dynamicfileUserIDStrict = true) must be supplied"))
} else {
userMappings = append(userMappings, userMapping)
}
}

for _, roleMapping := range dynamicFileData.RoleMappings {
if roleMapping.RoleARN == "" {
errs = append(errs, fmt.Errorf("Value for rolearn must be supplied"))
key := roleMapping.RoleARN
if m.userIDStrict {
key = roleMapping.UserId
}
if key == "" {
errs = append(errs, fmt.Errorf("Value for rolearn or userid(if dynamicfileUserIDStrict = true) must be supplied"))
} else {
roleMappings = append(roleMappings, roleMapping)
}
Expand All @@ -184,12 +193,18 @@ func (ms *DynamicFileMapStore) saveMap(
ms.awsAccounts = make(map[string]interface{})

for _, user := range userMappings {
canonicalizedARN, _ := arn.Canonicalize(strings.ToLower(user.UserARN))
ms.users[canonicalizedARN] = user
key, _ := arn.Canonicalize(strings.ToLower(user.UserARN))
if ms.userIDStrict {
key = user.UserId
}
ms.users[key] = user
}
for _, role := range roleMappings {
canonicalizedARN, _ := arn.Canonicalize(strings.ToLower(role.RoleARN))
ms.roles[canonicalizedARN] = role
key, _ := arn.Canonicalize(strings.ToLower(role.RoleARN))
if ms.userIDStrict {
key = role.UserId
}
ms.roles[key] = role
}
for _, awsAccount := range awsAccounts {
ms.awsAccounts[awsAccount] = nil
Expand Down
Loading

0 comments on commit 42d9d9b

Please sign in to comment.