diff --git a/.github/DEVELOPER.md b/.github/DEVELOPER.md index b7e8e65..549de67 100644 --- a/.github/DEVELOPER.md +++ b/.github/DEVELOPER.md @@ -8,15 +8,33 @@ * kubebuilder ### Quickstart -``` -$ git clone https://github.com/keikoproj/iam-manager -$ cd iam-manager -$ go mod -$ go vendor -``` +First, go and fork the Github repo to your own personal project. Once that's +done, set up a local build environment off of the original Github repo. Then we +add in your fork'ed repo as a new target for doing git pushes. + + $ go clean -modcache + $ go get -v github.com/keikoproj/iam-manager + $ cd "$(go env GOPATH)/src/github.com/keikoproj/iam-manager" + $ make test + $ go mod vendor + $ git remote add myfork + +### Install Kubebuilder + +Kubebuilder is a requirement for the testsuite.. you can install it quickly +on your own or with our make target: + + $ make kubebuilder ### Build project -``` -$ make -``` \ No newline at end of file + + $ make + +### Running Tests + +There are several environment variables that must be set in order for the +test suite to work. The [Makefile](/Makefile) sets these for you, so please +use the `make test` target to run tests: + + $ make test diff --git a/.gitignore b/.gitignore index 1f7c263..43704e1 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,8 @@ vendor/ mocks/ gomock*/ +kubebuilder* #IDE files .idea/ -bin/ \ No newline at end of file +bin/ diff --git a/.travis.yml b/.travis.yml index 3a5f992..ce4ece2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,9 +19,7 @@ before_install: skip install: # install kube builder so BDD test cases can work - - wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v2.2.0/kubebuilder_2.2.0_linux_amd64.tar.gz - - tar -zxvf kubebuilder_2.2.0_linux_amd64.tar.gz - - sudo mv kubebuilder_2.2.0_linux_amd64 /usr/local/kubebuilder + - make kubebuilder jobs: include: diff --git a/Makefile b/Makefile index db148cc..cf6c250 100644 --- a/Makefile +++ b/Makefile @@ -1,30 +1,43 @@ - # Image URL to use all building/pushing image targets -IMG ?= keikoproj/iam-manager:latest +IMG ?= keikoproj/iam-manager:latest + +# Tools required to run the full suite of tests properly +OSNAME ?= $(shell uname -s | tr A-Z a-z) +KUBEBUILDER_VER ?= 2.2.0 +KUBEBUILDER_ARCH ?= amd64 + # Produce CRDs that work back to Kubernetes 1.11 (no version conversion) CRD_OPTIONS ?= "crd:trivialVersions=true" +KUBECONFIG ?= $(HOME)/.kube/config +LOCAL ?= true +ALLOWED_POLICY_ACTION ?= s3:,sts:,ec2:Describe,acm:Describe,acm:List,acm:Get,route53:Get,route53:List,route53:Create,route53:Delete,route53:Change,kms:Decrypt,kms:Encrypt,kms:ReEncrypt,kms:GenerateDataKey,kms:DescribeKey,dynamodb:,secretsmanager:GetSecretValue,es:,sqs:SendMessage,sqs:ReceiveMessage,sqs:DeleteMessage,SNS:Publish,sqs:GetQueueAttributes,sqs:GetQueueUrl +RESTRICTED_POLICY_RESOURCES ?= policy-resource +RESTRICTED_S3_RESOURCES ?= s3-resource +AWS_ACCOUNT_ID ?= 123456789012 +AWS_REGION ?= us-west-2 +MANAGED_POLICIES ?= arn:aws:iam::123456789012:policy/SOMETHING +MANAGED_PERMISSION_BOUNDARY_POLICY ?= arn:aws:iam::1123456789012:role/iam-manager-permission-boundary +CLUSTER_NAME ?= k8s_test_keiko +CLUSTER_OIDC_ISSUER_URL ?= https://google.com/OIDC +DEFAULT_TRUST_POLICY ?= '{"Version": "2012-10-17", "Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:aws:iam::AWS_ACCOUNT_ID:oidc-provider/OIDC_PROVIDER"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"OIDC_PROVIDER:sub": "system:serviceaccount:{{.NamespaceName}}:SERVICE_ACCOUNT_NAME"}}}, {"Effect": "Allow","Principal": {"AWS": ["arn:aws:iam::{{.AccountID}}:role/trust_role"]},"Action": "sts:AssumeRole"}]}' + # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) -GOBIN=$(shell go env GOPATH)/bin + GOBIN := $(shell go env GOPATH)/bin else -GOBIN=$(shell go env GOBIN) + GOBIN := $(shell go env GOBIN) endif all: manager -setup: ; $(info $(M) setting up env variables for test…) @ ## Setup env variables -export LOCAL=true -export ALLOWED_POLICY_ACTION=s3:,sts:,ec2:Describe,acm:Describe,acm:List,acm:Get,route53:Get,route53:List,route53:Create,route53:Delete,route53:Change,kms:Decrypt,kms:Encrypt,kms:ReEncrypt,kms:GenerateDataKey,kms:DescribeKey,dynamodb:,secretsmanager:GetSecretValue,es:,sqs:SendMessage,sqs:ReceiveMessage,sqs:DeleteMessage,SNS:Publish,sqs:GetQueueAttributes,sqs:GetQueueUrl -export RESTRICTED_POLICY_RESOURCES=policy-resource -export RESTRICTED_S3_RESOURCES=s3-resource -export AWS_ACCOUNT_ID=123456789012 -export AWS_REGION=us-west-2 -export MANAGED_POLICIES=arn:aws:iam::123456789012:policy/SOMETHING -export MANAGED_PERMISSION_BOUNDARY_POLICY=arn:aws:iam::1123456789012:role/iam-manager-permission-boundary -export CLUSTER_NAME=k8s_test_keiko -export CLUSTER_OIDC_ISSUER_URL=https://google.com/OIDC -export DEFAULT_TRUST_POLICY={"Version": "2012-10-17", "Statement": [{"Effect": "Allow","Principal": {"Federated": "arn:aws:iam::AWS_ACCOUNT_ID:oidc-provider/OIDC_PROVIDER"},"Action": "sts:AssumeRoleWithWebIdentity","Condition": {"StringEquals": {"OIDC_PROVIDER:sub": "system:serviceaccount:{{.NamespaceName}}:SERVICE_ACCOUNT_NAME"}}}, {"Effect": "Allow","Principal": {"AWS": ["arn:aws:iam::{{.AccountID}}:role/trust_role"]},"Action": "sts:AssumeRole"}]} +.PHONY: kubebuilder +kubebuilder: + @echo "Downloading and installing Kubebuilder - this requires sudo privileges" + curl -fsSL -O "https://github.com/kubernetes-sigs/kubebuilder/releases/download/v$(KUBEBUILDER_VER)/kubebuilder_$(KUBEBUILDER_VER)_$(OSNAME)_$(KUBEBUILDER_ARCH).tar.gz" + rm -rf kubebuilder && mkdir -p kubebuilder + tar -zxvf kubebuilder_$(KUBEBUILDER_VER)_$(OSNAME)_$(KUBEBUILDER_ARCH).tar.gz --strip-components 1 -C kubebuilder + sudo cp -rf kubebuilder /usr/local mock: go get -u github.com/golang/mock/mockgen @@ -34,7 +47,19 @@ mock: done # Run tests -test: setup mock generate fmt manifests +test: mock generate fmt manifests + KUBECONFIG=$(KUBECONFIG) \ + LOCAL=$(LOCAL) \ + ALLOWED_POLICY_ACTION=$(ALLOWED_POLICY_ACTION) \ + RESTRICTED_POLICY_RESOURCES=$(RESTRICTED_POLICY_RESOURCES) \ + RESTRICTED_S3_RESOURCES=$(RESTRICTED_S3_RESOURCES) \ + AWS_ACCOUNT_ID=$(AWS_ACCOUNT_ID) \ + AWS_REGION=$(AWS_REGION) \ + MANAGED_POLICIES=$(MANAGED_POLICIES) \ + MANAGED_PERMISSION_BOUNDARY_POLICY=$(MANAGED_PERMISSION_BOUNDARY_POLICY) \ + CLUSTER_NAME=$(CLUSTER_NAME) \ + CLUSTER_OIDC_ISSUER_URL="$(CLUSTER_OIDC_ISSUER_URL)" \ + DEFAULT_TRUST_POLICY=$(DEFAULT_TRUST_POLICY) \ go test ./... -coverprofile cover.out # Build manager binary diff --git a/controllers/iamrole_controller.go b/controllers/iamrole_controller.go index e37a793..7729b70 100644 --- a/controllers/iamrole_controller.go +++ b/controllers/iamrole_controller.go @@ -73,6 +73,7 @@ func (r *IamroleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { log := log.Logger(ctx, "controllers", "iamrole_controller", "Reconcile") log.WithValues("iamrole", req.NamespacedName) log.Info("Start of the request") + //Get the resource var iamRole iammanagerv1alpha1.Iamrole @@ -80,13 +81,20 @@ func (r *IamroleReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { return ctrl.Result{}, ignoreNotFound(err) } - roleName := fmt.Sprintf("k8s-%s", iamRole.ObjectMeta.Name) - - if config.Props.DeriveNameFromNamespace() && config.Props.MaxRolesAllowed() == 1 { - roleName = fmt.Sprintf("k8s-%s", iamRole.ObjectMeta.Namespace) + // TODO: Remove the need to do this here. + // + // We are generating the roleName here for the potential call to the + // delete function. The right way to do this is to record the created + // IAM role ARN in the IamRole object itself, so that we can then + // guarantee the delete even if the operator has changed their + // iam.role.pattern setting. + roleName, err := utils.GenerateRoleName(ctx, iamRole, *config.Props) + if err != nil { + r.Recorder.Event(&iamRole, v1.EventTypeWarning, string(iammanagerv1alpha1.Error), "Unable to construct iam role name to error "+err.Error()) + return ctrl.Result{RequeueAfter: 30 * time.Second}, nil } - // Isit being deleted? + // Is it being deleted? if iamRole.ObjectMeta.DeletionTimestamp.IsZero() { //Good. This is not Delete use case //Lets check if this is very first time use case @@ -132,12 +140,15 @@ func (r *IamroleReconciler) HandleReconcile(ctx context.Context, req ctrl.Reques log = log.WithValues("iam_role_cr", iamRole.Name) log.Info("state of the custom resource ", "state", iamRole.Status.State) - roleName := fmt.Sprintf("k8s-%s", iamRole.ObjectMeta.Name) + roleName, err := utils.GenerateRoleName(ctx, *iamRole, *config.Props) - if config.Props.DeriveNameFromNamespace() && config.Props.MaxRolesAllowed() == 1 { - roleName = fmt.Sprintf("k8s-%s", iamRole.ObjectMeta.Namespace) - } log.V(1).Info("roleName constructed successfully", "roleName", roleName) + if err != nil { + r.Recorder.Event(iamRole, v1.EventTypeWarning, string(iammanagerv1alpha1.Error), "Unable to construct iam role name to error "+err.Error()) + // It is not clear to me that we want to requeue here - as this is a fairly permanent + // error. Is there a better pattern here? + return ctrl.Result{RequeueAfter: 30 * time.Second}, nil + } input, status, err := r.ConstructCreateIAMRoleInput(ctx, iamRole, roleName) if err != nil { diff --git a/controllers/iamrole_controller_test.go b/controllers/iamrole_controller_test.go index 462c10d..5744841 100644 --- a/controllers/iamrole_controller_test.go +++ b/controllers/iamrole_controller_test.go @@ -9,7 +9,6 @@ import ( ) var _ = Describe("IamroleController", func() { - Describe("When checking a StatusUpdatePredicate", func() { instance := StatusUpdatePredicate{} diff --git a/docs/Configmap_Properties.md b/docs/Configmap_Properties.md index d1c79be..3ced973 100644 --- a/docs/Configmap_Properties.md +++ b/docs/Configmap_Properties.md @@ -8,12 +8,48 @@ This document explains configmap variables. | aws.accountId | AWS account ID where IAM roles are created| |Optional | | iam.managed.policies | User managed IAM policies | |Optional | | iam.managed.permission.boundary.policy| User managed permission boundary policy|k8s-iam-manager-cluster-permission-boundary |Required | -| webhook.enabled | Enable webhook? | false | Required | -| iam.role.max.limit.per.namespace | Maximum number of roles per namespace | 1 | Required | -| aws.region | AWS Region | us-west-2 | Required | -| iam.default.trust.policy| Default trust policy role. This must follow v1alpha1.AssumeRolePolicyDocument syntax| | Optional | -| iam.role.derive.from.namespace | Derive iam role name from namespace? if true it will be k8s- | false | Optional| +| webhook.enabled | Enable webhook? | `false | Required | +| iam.role.max.limit.per.namespace | Maximum number of roles per namespace | 1 | Required | +| aws.region | AWS Region | `us-west-2` | Required | +| iam.default.trust.policy | Default trust policy role. This must follow v1alpha1.AssumeRolePolicyDocument syntax| | Optional | +| [iam.role.pattern](#iamrolepattern) | See docs below... | `k8s-{{ .ObjectMeta.Name }}` | Optional | | controller.desired.frequency | Controller frequency to check the state of the world (in seconds) | 300 | Optional | | k8s.cluster.name | Name of the cluster | | Optional | | k8s.cluster.oidc.issuer.url | OIDC issuer of the cluster | | Optional | -| iam.irsa.enabled | Enable IRSA option? | false | Optional | \ No newline at end of file +| iam.irsa.enabled | Enable IRSA option? | `false` | Optional | + + +## `iam.role.pattern` + +[template]: https://golang.org/pkg/text/template/ +[iamrole]: /api/v1alpha1/iamrole_types.go + +_Default_: `k8s-{{ .ObjectMeta.Name }}` + +All IAM roles created by the controller will use this [GoLang template][template] +to generate the final IAM Role Name. The default setting works fine if you have +a single cluster - but if you want to operate multiple clusters in the same AWS +account you will need to make sure the controllers do not conflict. + +The [`Iamrole`][iamrole] object is passed into the Go templating engine, enabling +you to use any object field found in that role. For example +`mycluster-{{ .ObjectMeta.Namespace }}-{{ .ObjectMeta.Name }}`. + +All IAM roles created and managed by the controller will use this prefix. This +helps organize IAM roles within your AWS Account, and can be used to ensure +uniqueness between different EKS Clusters within the same AWS account. + +**Critical Note: Read [this](#note-about-changing-iamroleprefix-and-iamroleseparator) +before changing this setting** + +**Note: Changes to your IAM Policy may be required if you customize this** + +## Note about changing `iam.role.pattern` + +If you have existing `IAMRole` resources in your cluster, and you make a change to +the `iam.role.pattern` setting - the controller will reconcile the situation by +creating NEW IAM roles. It will _not_ however clean up the old roles - thus you +will have left over unused IAM roles in your account. + +Get these settings right from the beginning, or be prepared to clean up the left +over roles. diff --git a/go.mod b/go.mod index df07710..6f7c64e 100644 --- a/go.mod +++ b/go.mod @@ -10,8 +10,9 @@ require ( github.com/onsi/gomega v1.8.1 github.com/pborman/uuid v1.2.0 github.com/pkg/errors v0.8.1 - golang.org/x/mod v0.4.0 // indirect - golang.org/x/tools v0.0.0-20201221201019-196535612888 // indirect + golang.org/x/mod v0.4.1 // indirect + golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c // indirect + golang.org/x/tools v0.1.0 // indirect gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 k8s.io/api v0.17.2 k8s.io/apimachinery v0.17.2 diff --git a/go.sum b/go.sum index 2fb2880..ec4b678 100644 --- a/go.sum +++ b/go.sum @@ -333,10 +333,11 @@ golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd h1:nTDtHvHSdCn1m6ITfMRqtOd/9+7a3s8RBNOZ3eYZzJA= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -351,11 +352,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b h1:0mm1VjtFUOIlE1SbDlwjYaDxZVDP2S5ou6y0gSgXHu8= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -365,12 +363,10 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e h1:o3PsSEY8E4eXWkXrIP9YJALUkVZqzHJT5DOasTyn8Vs= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -380,25 +376,23 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456 h1:ng0gs1AKnRRuEMZoTLLlbOd+C17zUDepwGQBb/n+JVg= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69 h1:rOhMmluY6kLMhdnrivzec6lLgaVbMHMn2ISQXJeJ5EM= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2 h1:+DCIGbF/swA92ohVg0//6X2IVY3KZs6p9mix0ziNYJM= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= @@ -414,38 +408,18 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72 h1:bw9doJza/SFBEweII/rHQh338oozWyiFsBRHtrflcws= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200428140416-006b16f6cf7f h1:S58tgFwC/PUuXkZBbUfGloERWUkLz9WBY3HFy9BMLaM= -golang.org/x/tools v0.0.0-20200428140416-006b16f6cf7f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200428204708-317da45f2f19 h1:uU2/31wMz41c/26KcY61Rq5vekiLPBGBXGackzSt4tM= -golang.org/x/tools v0.0.0-20200428204708-317da45f2f19/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32 h1:Xvf3ZQTm5bjXPxhI7g+dwqsCqadK1rcNtwtszuatetk= -golang.org/x/tools v0.0.0-20200428211428-0c9eba77bc32/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375 h1:SjQ2+AKWgZLc1xej6WSzL+Dfs5Uyd5xcZH1mGC411IA= -golang.org/x/tools v0.0.0-20200522201501-cb1345f3a375/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200526224456-8b020aee10d2 h1:21BqcH/onxtGHn1A2GDOJjZnbt4Nlez629S3eaR+eYs= -golang.org/x/tools v0.0.0-20200526224456-8b020aee10d2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200529172331-a64b76657301 h1:G6CNEgFU8/XwexSnuFw+Jq/WePjRitgy6ofBcPnAIPo= -golang.org/x/tools v0.0.0-20200529172331-a64b76657301/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200530233709-52effbd89c51 h1:Wec8/IO8hAraBf0it7/dPQYOslIrgM938wZYNkLnOYc= -golang.org/x/tools v0.0.0-20200530233709-52effbd89c51/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200606014950-c42cb6316fb6 h1:5Y8c5HBW6hBYnGEE3AbJPV0R8RsQmg1/eaJrpvasns0= -golang.org/x/tools v0.0.0-20200606014950-c42cb6316fb6/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50 h1:59syOWj4+Fl+op4LL8fX1kO7HmbdEWfxlw4tcGvH+y0= -golang.org/x/tools v0.0.0-20200609164405-eb789aa7ce50/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20201221201019-196535612888 h1:7caG9TJP3w/Lk3qVEGny32sb4gFJxPq0nVCYCHGi5Gw= -golang.org/x/tools v0.0.0-20201221201019-196535612888/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210119195117-a46736d9d962 h1:2W5DpBYpoX3ZovvDTg1qC2gXJEa0dXKkZVxUr3xiQdY= +golang.org/x/tools v0.0.0-20210119195117-a46736d9d962/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.0.1 h1:xyiBuvkD2g5n7cYzx6u2sxQvsAy4QJsZFCzGVdzOXZ0= @@ -454,10 +428,8 @@ gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40 gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/appengine v1.1.0 h1:igQkv0AAhEIvTEpD5LIpAfav2eeVO9HBTjvKHVJPRSs= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= @@ -467,7 +439,6 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -483,9 +454,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -507,7 +476,6 @@ k8s.io/component-base v0.17.2/go.mod h1:zMPW3g5aH7cHJpKYQ/ZsGMcgbsA/VyhEugF3QT1a k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/klog v0.3.0 h1:0VPpR+sizsiivjIfIAQH/rl8tan6jvWkS7lU+0di3lE= k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8= k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I= @@ -525,7 +493,6 @@ rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/controller-runtime v0.5.2 h1:pyXbUfoTo+HA3jeIfr0vgi+1WtmNh0CwlcnQGLXwsSw= sigs.k8s.io/controller-runtime v0.5.2/go.mod h1:JZUwSMVbxDupo0lTJSSFP5pimEyxGynROImSsqIOx1A= sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= -sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06 h1:zD2IemQ4LmOcAumeiyDWXKUI2SO0NYDe3H6QGvPOVgU= sigs.k8s.io/structured-merge-diff v1.0.1-0.20191108220359-b1b620dd3f06/go.mod h1:/ULNhyfzRopfcjskuui0cTITekDduZ7ycKN3oUT9R18= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/internal/config/constants.go b/internal/config/constants.go index 2ab1efc..3392e98 100644 --- a/internal/config/constants.go +++ b/internal/config/constants.go @@ -26,7 +26,7 @@ const ( propertyAwsRegion = "aws.region" //enable webhook property - propertAWSAccountID = "aws.accountId" + propertyAWSAccountID = "aws.accountId" // user managed policies propertyManagedPolicies = "iam.managed.policies" @@ -37,12 +37,12 @@ const ( //enable webhook property propertyWebhookEnabled = "webhook.enabled" + //golang-templated pattern to use for iam role name generation + propertyIamRolePattern = "iam.role.pattern" + //max allowed aws iam roles per namespace propertyMaxIamRoles = "iam.role.max.limit.per.namespace" - //propertyDeriveNameFromNameSpace is a bool value and can be used to configure the name construction - propertyDeriveNameFromNameSpace = "iam.role.derive.from.namespace" - //propertyDesiredStateFrequency is a configurable param to make sure to check the external state (in seconds). default to 30 mins (1800 seconds) propertyDesiredStateFrequency = "controller.desired.frequency" diff --git a/internal/config/properties.go b/internal/config/properties.go index cd653ec..f36fb0e 100644 --- a/internal/config/properties.go +++ b/internal/config/properties.go @@ -28,12 +28,12 @@ type Properties struct { awsRegion string isWebhookEnabled string maxRolesAllowed int - deriveNameFromNamespace string controllerDesiredFrequency int clusterName string isIRSAEnabled string clusterOIDCIssuerUrl string defaultTrustPolicy string + iamRolePattern string } func init() { @@ -79,10 +79,10 @@ func LoadProperties(env string, cm ...*v1.ConfigMap) error { managedPermissionBoundaryPolicy: os.Getenv("MANAGED_PERMISSION_BOUNDARY_POLICY"), awsRegion: os.Getenv("AWS_REGION"), isWebhookEnabled: os.Getenv("ENABLE_WEBHOOK"), - deriveNameFromNamespace: os.Getenv("DERIVE_NAME_FROM_NAMESPACE"), clusterName: os.Getenv("CLUSTER_NAME"), clusterOIDCIssuerUrl: os.Getenv("CLUSTER_OIDC_ISSUER_URL"), defaultTrustPolicy: os.Getenv("DEFAULT_TRUST_POLICY"), + iamRolePattern: os.Getenv("IAM_ROLE_PATTERN"), } return nil } @@ -113,13 +113,6 @@ func LoadProperties(env string, cm ...*v1.ConfigMap) error { Props.isWebhookEnabled = "false" } - deriveNameFromNS := cm[0].Data[propertyDeriveNameFromNameSpace] - if deriveNameFromNS == "true" { - Props.deriveNameFromNamespace = "true" - } else { - Props.deriveNameFromNamespace = "false" - } - awsRegion := cm[0].Data[propertyAwsRegion] if awsRegion != "" { Props.awsRegion = awsRegion @@ -149,7 +142,7 @@ func LoadProperties(env string, cm ...*v1.ConfigMap) error { Props.controllerDesiredFrequency = 1800 } - awsAccountID := cm[0].Data[propertAWSAccountID] + awsAccountID := cm[0].Data[propertyAWSAccountID] // Load AWS account ID if Props.awsAccountID == "" && awsAccountID == "" { awsAccountID, err := awsapi.NewSTS(Props.awsRegion).GetAccountID(context.Background()) @@ -161,6 +154,13 @@ func LoadProperties(env string, cm ...*v1.ConfigMap) error { Props.awsAccountID = awsAccountID } + iamRolePattern := cm[0].Data[propertyIamRolePattern] + if iamRolePattern == "" { + Props.iamRolePattern = "k8s-{{ .ObjectMeta.Name }}" + } else { + Props.iamRolePattern = iamRolePattern + } + managedPermissionBoundaryPolicyArn := cm[0].Data[propertyPermissionBoundary] if managedPermissionBoundaryPolicyArn == "" { @@ -243,12 +243,8 @@ func (p *Properties) IsWebHookEnabled() bool { return resp } -func (p *Properties) DeriveNameFromNamespace() bool { - resp := false - if p.deriveNameFromNamespace == "true" { - resp = true - } - return resp +func (p *Properties) IamRolePattern() string { + return p.iamRolePattern } func (p *Properties) MaxRolesAllowed() int { diff --git a/internal/config/properties_test.go b/internal/config/properties_test.go index 16c8f22..00839e1 100644 --- a/internal/config/properties_test.go +++ b/internal/config/properties_test.go @@ -88,9 +88,9 @@ func (s *PropertiesSuite) TestLoadPropertiesSuccessWithDefaults(c *check.C) { c.Assert(Props.MaxRolesAllowed(), check.Equals, 1) c.Assert(Props.ControllerDesiredFrequency(), check.Equals, 1800) c.Assert(Props.IsWebHookEnabled(), check.Equals, false) - c.Assert(Props.DeriveNameFromNamespace(), check.Equals, false) c.Assert(Props.AWSAccountID(), check.Equals, "123456789012") c.Assert(strings.HasPrefix(Props.ManagedPermissionBoundaryPolicy(), "arn:aws:iam:"), check.Equals, true) + c.Assert(Props.IamRolePattern(), check.Equals, "k8s-{{ .ObjectMeta.Name }}") //when an emty string passed split strings gives you array of 1 with "" c.Assert(len(Props.ManagedPolicies()), check.Equals, 1) c.Assert(Props.ManagedPolicies()[0], check.Equals, "") @@ -103,8 +103,7 @@ func (s *PropertiesSuite) TestLoadPropertiesSuccessWithDefaultsManagedPoliciesWi Data: map[string]string{ "iam.managed.permission.boundary.policy": "iam-manager-permission-boundary", "aws.accountId": "123456789012", - "iam.managed.policies": "DescribeEC2", - + "iam.managed.policies": "DescribeEC2", }, } err := LoadProperties("", cm) @@ -113,7 +112,6 @@ func (s *PropertiesSuite) TestLoadPropertiesSuccessWithDefaultsManagedPoliciesWi c.Assert(Props.MaxRolesAllowed(), check.Equals, 1) c.Assert(Props.ControllerDesiredFrequency(), check.Equals, 1800) c.Assert(Props.IsWebHookEnabled(), check.Equals, false) - c.Assert(Props.DeriveNameFromNamespace(), check.Equals, false) c.Assert(Props.AWSAccountID(), check.Equals, "123456789012") c.Assert(strings.HasPrefix(Props.ManagedPermissionBoundaryPolicy(), "arn:aws:iam:"), check.Equals, true) //when an emty string passed split strings gives you array of 1 with "" @@ -131,13 +129,14 @@ func (s *PropertiesSuite) TestLoadPropertiesSuccessWithCustom(c *check.C) { "iam.role.derive.from.namespace": "true", "controller.desired.frequency": "30", "iam.role.max.limit.per.namespace": "5", + "iam.role.pattern": "pfx-{{ .ObjectMeta.Name }}", }, } err := LoadProperties("", cm) c.Assert(err, check.IsNil) c.Assert(Props.MaxRolesAllowed(), check.Equals, 5) c.Assert(Props.ControllerDesiredFrequency(), check.Equals, 30) - c.Assert(Props.DeriveNameFromNamespace(), check.Equals, true) + c.Assert(Props.IamRolePattern(), check.Equals, "pfx-{{ .ObjectMeta.Name }}") } func (s *PropertiesSuite) TestGetAllowedPolicyAction(c *check.C) { @@ -180,11 +179,6 @@ func (s *PropertiesSuite) TestIsWebhookEnabled(c *check.C) { c.Assert(value, check.Equals, false) } -func (s *PropertiesSuite) TestDeriveNameFromNamespace(c *check.C) { - value := Props.DeriveNameFromNamespace() - c.Assert(value, check.Equals, false) -} - func (s *PropertiesSuite) TestControllerDesiredFrequency(c *check.C) { value := Props.ControllerDesiredFrequency() c.Assert(value, check.Equals, 0) diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 881c43f..518a52e 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -125,3 +125,28 @@ func DefaultTrustPolicy(ctx context.Context, trustPolicyDoc string, ns string) ( return &trustPolicy, nil } + +// GenerateRoleName returns a roleName that should be created in IAM using +// the supplied iam.role.pattern. This pattern can be customized by the +// end-user. +func GenerateRoleName(ctx context.Context, iamRole iammanagerv1alpha1.Iamrole, props config.Properties) (string, error) { + log := log.Logger(ctx, "internal.utils.utils", "GenerateRoleNam") + tmpl, err := template.New("rolename").Parse(props.IamRolePattern()) + if err != nil { + msg := "unable to parse supplied iam.role.pattern" + log.Error(err, msg) + return "", err + } + + // Write the template output into a buffer and then grab that as a string. + // There is no way in GoLang natively to do this. + buf := &bytes.Buffer{} + err = tmpl.ExecuteTemplate(buf, "rolename", iamRole) + if err != nil { + msg := "unable to execute iam.role.pattern template against the iamrole object" + log.Error(err, msg) + return "", err + } + + return buf.String(), nil +} diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index 7bb9c08..381b4a6 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -8,6 +8,7 @@ import ( "github.com/keikoproj/iam-manager/internal/config" "github.com/keikoproj/iam-manager/internal/utils" "gopkg.in/check.v1" + v12 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" "testing" ) @@ -26,6 +27,13 @@ func TestUtilsTestSuite(t *testing.T) { func (s *UtilsTestSuite) SetUpTest(c *check.C) { s.ctx = context.Background() s.mockCtrl = gomock.NewController(s.t) + + // Always reset the config.Props between tests - we make changes to them + // during certain tests, and we want to ensure that they are predictable + // between each test. + config.Props = nil + err := config.LoadProperties("LOCAL") + c.Assert(err, check.IsNil) } func (s *UtilsTestSuite) TearDownTest(c *check.C) { @@ -516,3 +524,104 @@ func (s *UtilsTestSuite) TestGetTrustPolicyWithIRSAAnnotationAndServiceRoleInReq c.Assert(roleString, check.Equals, string(expected)) } + +func (s *UtilsTestSuite) TestGenerateNameFunction(c *check.C) { + cm := &v12.ConfigMap{ + Data: map[string]string{ + "aws.accountId": "123456789012", // Required mock for testing + "iam.role.derive.from.namespace": "false", + "iam.role.pattern": "pfx+{{ .ObjectMeta.Name }}", + }, + } + config.Props = nil + err := config.LoadProperties("", cm) + c.Assert(err, check.IsNil) + + resource := &v1alpha1.Iamrole{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + Namespace: "test-ns", + }, + } + name, err := utils.GenerateRoleName(s.ctx, *resource, *config.Props) + c.Assert(name, check.Equals, "pfx+foo") + c.Assert(err, check.IsNil) +} + +func (s *UtilsTestSuite) TestGenerateNameFunctionWithNamespace(c *check.C) { + cm := &v12.ConfigMap{ + Data: map[string]string{ + "aws.accountId": "123456789012", // Required mock for testing + "iam.role.derive.from.namespace": "true", + "iam.role.pattern": "pfx+{{ .ObjectMeta.Namespace}}+{{ .ObjectMeta.Name }}", + }, + } + config.Props = nil + err := config.LoadProperties("", cm) + c.Assert(err, check.IsNil) + + resource := &v1alpha1.Iamrole{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + Namespace: "test-ns", + }, + } + roleName, err := utils.GenerateRoleName(s.ctx, *resource, *config.Props) + c.Assert(roleName, check.Equals, "pfx+test-ns+foo") + c.Assert(err, check.IsNil) +} + +func assertPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } + }() + f() +} + +func (s *UtilsTestSuite) TestGenerateNameFunctionBadTemplate(c *check.C) { + cm := &v12.ConfigMap{ + Data: map[string]string{ + "aws.accountId": "123456789012", // Required mock for testing + "iam.role.derive.from.namespace": "true", + "iam.role.pattern": "pfx+{{ invalid-template }}", + }, + } + config.Props = nil + err := config.LoadProperties("", cm) + c.Assert(err, check.IsNil) + + resource := &v1alpha1.Iamrole{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + Namespace: "test-ns", + }, + } + _, err = utils.GenerateRoleName(s.ctx, *resource, *config.Props) + c.Assert(err, check.NotNil) + c.Assert(err.Error(), check.Matches, ".*unexpected bad character.*") +} + +func (s *UtilsTestSuite) TestGenerateNameFunctionBadObjectReference(c *check.C) { + cm := &v12.ConfigMap{ + Data: map[string]string{ + "aws.accountId": "123456789012", // Required mock for testing + "iam.role.derive.from.namespace": "true", + "iam.role.pattern": "pfx+{{ .invalid.data }}", + }, + } + config.Props = nil + err := config.LoadProperties("", cm) + c.Assert(err, check.IsNil) + + resource := &v1alpha1.Iamrole{ + ObjectMeta: v1.ObjectMeta{ + Name: "foo", + Namespace: "test-ns", + }, + } + _, err = utils.GenerateRoleName(s.ctx, *resource, *config.Props) + c.Assert(err, check.NotNil) + c.Assert(err.Error(), check.Matches, ".*field invalid in type.*") +}