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

IAMIdentityMapping CRD Implementation #116

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,6 @@ cscope.*

/bazel-*
*.pyc

# local dot files
.envrc
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ default: build
GITHUB_REPO ?= sigs.k8s.io/aws-iam-authenticator
GORELEASER := $(shell command -v goreleaser 2> /dev/null)

.PHONY: build test format
.PHONY: build test format codegen

build:
ifndef GORELEASER
Expand All @@ -17,3 +17,6 @@ test:
format:
test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -d {} + | tee /dev/stderr)" || \
test -z "$$(find . -path ./vendor -prune -type f -o -name '*.go' -exec gofmt -w {} + | tee /dev/stderr)"

codegen:
./hack/update-codegen.sh
29 changes: 19 additions & 10 deletions cmd/aws-iam-authenticator/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import (
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"

"k8s.io/component-base/featuregate"
)

var cfgFile string
Expand All @@ -35,6 +37,8 @@ var rootCmd = &cobra.Command{
Short: "A tool to authenticate to Kubernetes using AWS IAM credentials",
}

var featureGates = featuregate.NewFeatureGate()

func main() {
Execute()
}
Expand All @@ -57,10 +61,12 @@ func init() {
"cluster-id",
"i",
"",
"Specify the cluster `ID`, a unique-per-cluster identifier for your aws-iam-authenticator installation.",
)
"Specify the cluster `ID`, a unique-per-cluster identifier for your aws-iam-authenticator installation.")
viper.BindPFlag("clusterID", rootCmd.PersistentFlags().Lookup("cluster-id"))
viper.BindEnv("clusterID", "KUBERNETES_AWS_AUTHENTICATOR_CLUSTER_ID")

featureGates.Add(config.DefaultFeatureGates)
featureGates.AddFlag(rootCmd.PersistentFlags())
}

func initConfig() {
Expand All @@ -76,7 +82,7 @@ func initConfig() {
}

func getConfig() (config.Config, error) {
christopherhein marked this conversation as resolved.
Show resolved Hide resolved
config := config.Config{
cfg := config.Config{
ClusterID: viper.GetString("clusterID"),
ServerEC2DescribeInstancesRoleARN: viper.GetString("server.ec2DescribeInstancesRoleARN"),
HostPort: viper.GetInt("server.port"),
Expand All @@ -85,22 +91,25 @@ func getConfig() (config.Config, error) {
KubeconfigPregenerated: viper.GetBool("server.kubeconfigPregenerated"),
StateDir: viper.GetString("server.stateDir"),
Address: viper.GetString("server.address"),
Kubeconfig: viper.GetString("server.kubeconfig"),
Master: viper.GetString("server.master"),
FeatureGates: featureGates,
}
if err := viper.UnmarshalKey("server.mapRoles", &config.RoleMappings); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you have thoughts on upgrade? My first thought is that we should keep these in a deprecated state for the first version. If the mappings in the config are detected, only the config file is used and it takes precedence. This prevents the authenticator from failing on startup if the kubernetes config is not present but mappings in the config file are. Otherwise, upgrade is a bit more difficult.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, that's a good point.

return config, fmt.Errorf("invalid server role mappings: %v", err)
if err := viper.UnmarshalKey("server.mapRoles", &cfg.RoleMappings); err != nil {
return cfg, fmt.Errorf("invalid server role mappings: %v", err)
}
if err := viper.UnmarshalKey("server.mapUsers", &config.UserMappings); err != nil {
if err := viper.UnmarshalKey("server.mapUsers", &cfg.UserMappings); err != nil {
logrus.WithError(err).Fatal("invalid server user mappings")
}
if err := viper.UnmarshalKey("server.mapAccounts", &config.AutoMappedAWSAccounts); err != nil {
if err := viper.UnmarshalKey("server.mapAccounts", &cfg.AutoMappedAWSAccounts); err != nil {
logrus.WithError(err).Fatal("invalid server account mappings")
}

if config.ClusterID == "" {
return config, errors.New("cluster ID cannot be empty")
if cfg.ClusterID == "" {
return cfg, errors.New("cluster ID cannot be empty")
}

return config, nil
return cfg, nil
}

func getLogFormatter() logrus.Formatter {
Expand Down
95 changes: 88 additions & 7 deletions cmd/aws-iam-authenticator/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,23 @@ limitations under the License.
package main

import (
"flag"
"time"

"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/sample-controller/pkg/signals"
"sigs.k8s.io/aws-iam-authenticator/pkg/config"
"sigs.k8s.io/aws-iam-authenticator/pkg/controller"
clientset "sigs.k8s.io/aws-iam-authenticator/pkg/generated/clientset/versioned"
informers "sigs.k8s.io/aws-iam-authenticator/pkg/generated/informers/externalversions"
"sigs.k8s.io/aws-iam-authenticator/pkg/server"

"k8s.io/apimachinery/pkg/runtime"
k8sfake "k8s.io/client-go/kubernetes/fake"
"sigs.k8s.io/aws-iam-authenticator/pkg/generated/clientset/versioned/fake"

"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -33,13 +48,67 @@ var serverCmd = &cobra.Command{
Short: "Run a webhook validation server suitable that validates tokens using AWS IAM",
Long: ``,
Run: func(cmd *cobra.Command, args []string) {
config, err := getConfig()
var err error
var k8sconfig *rest.Config
var kubeClient kubernetes.Interface
var iamClient clientset.Interface
var iamInformerFactory informers.SharedInformerFactory
var noResyncPeriodFunc = func() time.Duration { return 0 }

stopCh := signals.SetupSignalHandler()

cfg, err := getConfig()
if err != nil {
logrus.Fatalf("%s", err)
}

server.New(config).Run()
logrus.Infof("Feature Gates %+v", cfg.FeatureGates)

if cfg.FeatureGates.Enabled(config.IAMIdentityMappingCRD) {
if cfg.Master != "" || cfg.Kubeconfig != "" {
k8sconfig, err = clientcmd.BuildConfigFromFlags(cfg.Master, cfg.Kubeconfig)
} else {
k8sconfig, err = rest.InClusterConfig()
}
if err != nil {
logrus.WithError(err).Fatal("can't create kubernetes config")
}

kubeClient, err = kubernetes.NewForConfig(k8sconfig)
if err != nil {
logrus.WithError(err).Fatal("can't create kubernetes client")
}

iamClient, err = clientset.NewForConfig(k8sconfig)
if err != nil {
logrus.WithError(err).Fatal("can't create iam authenticator client")
}

iamInformerFactory = informers.NewSharedInformerFactory(iamClient, time.Second*36000)

ctrl := controller.New(kubeClient, iamClient, iamInformerFactory.Iamauthenticator().V1alpha1().IAMIdentityMappings())
httpServer := server.New(cfg, iamInformerFactory.Iamauthenticator().V1alpha1().IAMIdentityMappings())
iamInformerFactory.Start(stopCh)

go func() {
httpServer.Run(stopCh)
}()

if err := ctrl.Run(2, stopCh); err != nil {
logrus.WithError(err).Fatal("controller exited")
}
} else {
kubeClient = k8sfake.NewSimpleClientset([]runtime.Object{}...)
iamClient = fake.NewSimpleClientset([]runtime.Object{}...)
iamInformerFactory = informers.NewSharedInformerFactory(iamClient, noResyncPeriodFunc())

iamInformerFactory.Start(stopCh)
httpServer := server.New(cfg, iamInformerFactory.Iamauthenticator().V1alpha1().IAMIdentityMappings())
go func() {
httpServer.Run(stopCh)
}()
<-stopCh
}
},
}

Expand All @@ -61,12 +130,24 @@ func init() {
"State `directory` for generated certificate and private key (should be a hostPath mount).")
viper.BindPFlag("server.stateDir", serverCmd.Flags().Lookup("state-dir"))

serverCmd.Flags().StringP(
"bind",
"b",
serverCmd.Flags().String("kubeconfig",
"",
"kubeconfig file path for using a local kubeconfig to configure the client to talk to the API server for the IAMIdentityMappings.")
viper.BindPFlag("server.kubeconfig", serverCmd.Flags().Lookup("kubeconfig"))
serverCmd.Flags().String("master",
"",
"master is the URL to the api server")
viper.BindPFlag("server.master", serverCmd.Flags().Lookup("master"))

serverCmd.Flags().String(
"address",
"127.0.0.1",
"IP Address to bind the server to listen to. (should be 127.0.0.1 or 0.0.0.0)")
viper.BindPFlag("server.bind", serverCmd.Flags().Lookup("bind"))
"IP Address to bind the server to listen to. (should be a 127.0.0.1 or 0.0.0.0)")
viper.BindPFlag("server.address", serverCmd.Flags().Lookup("address"))

fs := flag.NewFlagSet("", flag.ContinueOnError)
_ = fs.Parse([]string{})
flag.CommandLine = fs

rootCmd.AddCommand(serverCmd)
}
10 changes: 10 additions & 0 deletions example-role.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe put the examples in an examples dir?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sounds good!

apiVersion: iamauthenticator.k8s.aws/v1alpha1
kind: IAMIdentityMapping
metadata:
name: kubernetes-admin
spec:
arn: arn:aws:iam::XXXXXXXXXXXX:user/KubernetesAdmin
username: kubernetes-admin
groups:
- system:masters
118 changes: 79 additions & 39 deletions example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,80 @@
# may also need to rework other bits to work in your cluster (e.g., node labels).
#
# This was tested with a kubeadm-installed cluster.
---
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you should separate out the identity mapping stuff from the daemonset in the example, as it may be common to run the authenticator in other ways. Additionally, for simplicity I see them as two separate steps in the setup guide, first applying everything the authenticator needs to run in your cluster, and second, running the authenticator itself.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also we will need to change the daemonset container version at the bottom of this file.

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: aws-iam-authenticator
rules:
- apiGroups:
- iamauthenticator.k8s.aws
resources:
- "*"
verbs:
- "*"
- apiGroups:
- ""
resources:
- events
verbs:
- create
- update
- patch

---
apiVersion: v1
kind: ServiceAccount
metadata:
name: aws-iam-authenticator
namespace: kube-system

---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1beta1
metadata:
name: aws-iam-authenticator
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: aws-iam-authenticator
subjects:
- kind: ServiceAccount
name: aws-iam-authenticator
namespace: kube-system

---
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: iamidentitymappings.iamauthenticator.k8s.aws
spec:
group: iamauthenticator.k8s.aws
version: v1alpha1
scope: Cluster
names:
plural: iamidentitymappings
singular: iamidentitymapping
kind: IAMIdentityMapping
categories:
- all
validation:
openAPIV3Schema:
properties:
spec:
properties:
arn:
type: string
pattern: '.*'
christopherhein marked this conversation as resolved.
Show resolved Hide resolved
username:
type: string
pattern: '.*'
groups:
type: array
items:
type: string
pattern: '.*'

---
apiVersion: v1
kind: ConfigMap
Expand All @@ -32,45 +106,8 @@ data:
# (good choices are a random token or a domain name that will be unique to your cluster)
clusterID: my-dev-cluster.example.com
server:
# each mapRoles entry maps an IAM role to a username and set of groups
# Each username and group can optionally contain template parameters:
# 1) "{{AccountID}}" is the 12 digit AWS ID.
# 2) "{{SessionName}}" is the role session name.
mapRoles:
# statically map arn:aws:iam::000000000000:role/KubernetesAdmin to a cluster admin
- roleARN: arn:aws:iam::000000000000:role/KubernetesAdmin
username: kubernetes-admin
groups:
- system:masters

# map EC2 instances in my "KubernetesNode" role to users like
# "aws:000000000000:instance:i-0123456789abcdef0". Only use this if you
# trust that the role can only be assumed by EC2 instances. If an IAM user
# can assume this role directly (with sts:AssumeRole) they can control
# SessionName.
- roleARN: arn:aws:iam::000000000000:role/KubernetesNode
username: aws:{{AccountID}}:instance:{{SessionName}}
groups:
- system:bootstrappers
- aws:instances

# map federated users in my "KubernetesAdmin" role to users like
# "admin:alice-example.com". The SessionName is an arbitrary role name
# like an e-mail address passed by the identity provider. Note that if this
# role is assumed directly by an IAM User (not via federation), the user
# can control the SessionName.
- roleARN: arn:aws:iam::000000000000:role/KubernetesAdmin
username: admin:{{SessionName}}
groups:
- system:masters

# each mapUsers entry maps an IAM role to a static username and set of groups
mapUsers:
# map user IAM user Alice in 000000000000 to user "alice" in "system:masters"
- userARN: arn:aws:iam::000000000000:user/Alice
username: alice
groups:
- system:masters
# mapAccounts:
# - <AWS_ACCOUNT_ID>

---
apiVersion: extensions/v1beta1
Expand All @@ -90,6 +127,9 @@ spec:
labels:
k8s-app: aws-iam-authenticator
spec:
# use service account with access to
serviceAccountName: aws-iam-authenticator

# run on the host network (don't depend on CNI)
hostNetwork: true

Expand Down
15 changes: 15 additions & 0 deletions hack/boilerplate.go.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
Copyright The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
Loading