Add External Policies (AWS managed policy attachments)

This commit is contained in:
Matt Ouille 2019-10-28 14:23:45 -07:00 committed by Matt Ouille
parent fd99b3b42f
commit f025ff0e70
No known key found for this signature in database
GPG Key ID: 36DF664C07A67A08
20 changed files with 1077 additions and 18 deletions

View File

@ -33,6 +33,7 @@ type MockIAM struct {
InstanceProfiles map[string]*iam.InstanceProfile
Roles map[string]*iam.Role
RolePolicies []*rolePolicy
AttachedPolicies map[string][]*iam.AttachedPolicy
}
var _ iamiface.IAMAPI = &MockIAM{}

View File

@ -141,9 +141,47 @@ func (m *MockIAM) DeleteRole(request *iam.DeleteRoleInput) (*iam.DeleteRoleOutpu
return &iam.DeleteRoleOutput{}, nil
}
func (m *MockIAM) DeleteRoleWithContext(aws.Context, *iam.DeleteRoleInput, ...request.Option) (*iam.DeleteRoleOutput, error) {
panic("Not implemented")
}
func (m *MockIAM) DeleteRoleRequest(*iam.DeleteRoleInput) (*request.Request, *iam.DeleteRoleOutput) {
panic("Not implemented")
}
func (m *MockIAM) ListAttachedRolePolicies(input *iam.ListAttachedRolePoliciesInput) (*iam.ListAttachedRolePoliciesOutput, error) {
m.mutex.Lock()
defer m.mutex.Unlock()
klog.Infof("ListAttachedRolePolicies: %s", aws.StringValue(input.RoleName))
for _, r := range m.Roles {
if r.RoleName == input.RoleName {
role := aws.StringValue(r.RoleName)
return &iam.ListAttachedRolePoliciesOutput{
AttachedPolicies: m.AttachedPolicies[role],
}, nil
}
}
return &iam.ListAttachedRolePoliciesOutput{}, nil
}
func (m *MockIAM) ListAttachedRolePoliciesPages(input *iam.ListAttachedRolePoliciesInput, pager func(*iam.ListAttachedRolePoliciesOutput, bool) bool) error {
m.mutex.Lock()
defer m.mutex.Unlock()
klog.Infof("ListAttachedRolePolicies: %s", aws.StringValue(input.RoleName))
role := aws.StringValue(input.RoleName)
if pager(&iam.ListAttachedRolePoliciesOutput{
AttachedPolicies: m.AttachedPolicies[role],
}, true) {
return nil
}
return nil
}

View File

@ -82,6 +82,11 @@ func TestComplex(t *testing.T) {
runTestCloudformation(t, "complex.example.com", "complex", "v1alpha2", false, nil, true)
}
// TestExternalPolicies tests external policies output
func TestExternalPolicies(t *testing.T) {
runTestAWS(t, "externalpolicies.example.com", "externalpolicies", "v1alpha2", false, 1, true, false, nil, true, false)
}
func TestNoSSHKey(t *testing.T) {
runTestAWS(t, "nosshkey.example.com", "nosshkey", "v1alpha2", false, 1, true, false, nil, false, false)
}

View File

@ -57,6 +57,26 @@ The additional permissions are:
}
```
## Adding External Policies
At times you may want to attach policies shared to you by another AWS account or that are maintained by an outside application. You can specify managed policies through the `policyOverrides` spec field.
Policy Overrides are specified by their ARN on AWS and are grouped by their role type. See the example below:
```yaml
spec:
externalPolicies:
node:
- aws:arn:iam:123456789000:policy:test-policy
master:
- aws:arn:iam:123456789000:policy:test-policy
bastion:
- aws:arn:iam:123456789000:policy:test-policy
```
External Policy attachments are treated declaritively. Any policies declared will be attached to the role, any policies not specified will be detached _after_ new policies are attached. This does not replace or affect built in Kops policies in any way.
It's important to note that externalPolicies will only handle the attachment and detachment of policies, not creation, modification, or deletion.
## Adding Additional Policies

View File

@ -18,7 +18,7 @@
. $(dirname "${BASH_SOURCE}")/common.sh
WORK_DIR=`mktemp -d`
WORK_DIR=$(mktemp -d)
cleanup() {
chmod -R +w "${WORK_DIR}"
@ -26,19 +26,19 @@ cleanup() {
}
trap cleanup EXIT
mkdir -p ${WORK_DIR}/go/
ln -s ${GOPATH}/src/k8s.io/kops/vendor/ ${WORK_DIR}/go/src
mkdir -p "${WORK_DIR}/go/"
cp -R "${GOPATH}/src/k8s.io/kops/vendor/" "${WORK_DIR}/go/src"
unset GOBIN
GOPATH=${WORK_DIR}/go/ go install -v k8s.io/code-generator/cmd/conversion-gen/
cp ${WORK_DIR}/go/bin/conversion-gen ${GOPATH}/bin/
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/deepcopy-gen/
cp ${WORK_DIR}/go/bin/deepcopy-gen ${GOPATH}/bin/
env GOBIN="${WORK_DIR}/go/bin" GOPATH="${WORK_DIR}/go/" go install -v k8s.io/code-generator/cmd/conversion-gen/
cp "${WORK_DIR}/go/bin/conversion-gen" "${GOPATH}/bin/"
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/defaulter-gen/
cp ${WORK_DIR}/go/bin/defaulter-gen ${GOPATH}/bin/
env GOBIN="${WORK_DIR}/go/bin" GOPATH="${WORK_DIR}/go/" go install k8s.io/code-generator/cmd/deepcopy-gen/
cp "${WORK_DIR}/go/bin/deepcopy-gen" "${GOPATH}/bin/"
GOPATH=${WORK_DIR}/go/ go install k8s.io/code-generator/cmd/client-gen/
cp ${WORK_DIR}/go/bin/client-gen ${GOPATH}/bin/
env GOBIN="${WORK_DIR}/go/bin" GOPATH="${WORK_DIR}/go/" go install k8s.io/code-generator/cmd/defaulter-gen/
cp "${WORK_DIR}/go/bin/defaulter-gen" "${GOPATH}/bin/"
env GOBIN="${WORK_DIR}/go/bin" GOPATH="${WORK_DIR}/go/" go install k8s.io/code-generator/cmd/client-gen/
cp "${WORK_DIR}/go/bin/client-gen" "${GOPATH}/bin/"

View File

@ -670,6 +670,14 @@ spec:
(use to control whom can creates dns entries)
type: string
type: object
externalPolicies:
additionalProperties:
items:
type: string
type: array
description: ExternalPolicies allows the insertion of pre-existing managed
policies on IG Roles
type: object
fileAssets:
description: A collection of files assets for deployed cluster wide
items:

View File

@ -136,6 +136,8 @@ type ClusterSpec struct {
// 'external' do not apply updates automatically - they are applied manually or by an external system
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// ExternalPolicies allows the insertion of pre-existing managed policies on IG Roles
ExternalPolicies *map[string][]string `json:"externalPolicies,omitempty"`
// Additional policies to add for roles
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
// A collection of files assets for deployed cluster wide

View File

@ -130,6 +130,8 @@ type ClusterSpec struct {
// 'external' do not apply updates automatically - they are applied manually or by an external system
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// ExternalPolicies allows the insertion of pre-existing managed policies on IG Roles
ExternalPolicies *map[string][]string `json:"externalPolicies,omitempty"`
// Additional policies to add for roles
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
// A collection of files assets for deployed cluster wide

View File

@ -1674,6 +1674,7 @@ func autoConvert_v1alpha1_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
// WARNING: in.AdminAccess requires manual conversion: does not exist in peer-type
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
out.ExternalPolicies = in.ExternalPolicies
out.AdditionalPolicies = in.AdditionalPolicies
if in.FileAssets != nil {
in, out := &in.FileAssets, &out.FileAssets
@ -1991,6 +1992,7 @@ func autoConvert_kops_ClusterSpec_To_v1alpha1_ClusterSpec(in *kops.ClusterSpec,
// WARNING: in.KubernetesAPIAccess requires manual conversion: does not exist in peer-type
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
out.ExternalPolicies = in.ExternalPolicies
out.AdditionalPolicies = in.AdditionalPolicies
if in.FileAssets != nil {
in, out := &in.FileAssets, &out.FileAssets

View File

@ -651,6 +651,25 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
*out = new(string)
**out = **in
}
if in.ExternalPolicies != nil {
in, out := &in.ExternalPolicies, &out.ExternalPolicies
*out = new(map[string][]string)
if **in != nil {
in, out := *in, *out
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
}
if in.AdditionalPolicies != nil {
in, out := &in.AdditionalPolicies, &out.AdditionalPolicies
*out = new(map[string]string)

View File

@ -135,6 +135,8 @@ type ClusterSpec struct {
// 'external' do not apply updates automatically - they are applied manually or by an external system
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// ExternalPolicies allows the insertion of pre-existing managed policies on IG Roles
ExternalPolicies *map[string][]string `json:"externalPolicies,omitempty"`
// Additional policies to add for roles
AdditionalPolicies *map[string]string `json:"additionalPolicies,omitempty"`
// A collection of files assets for deployed cluster wide

View File

@ -1737,6 +1737,7 @@ func autoConvert_v1alpha2_ClusterSpec_To_kops_ClusterSpec(in *ClusterSpec, out *
out.KubernetesAPIAccess = in.KubernetesAPIAccess
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
out.ExternalPolicies = in.ExternalPolicies
out.AdditionalPolicies = in.AdditionalPolicies
if in.FileAssets != nil {
in, out := &in.FileAssets, &out.FileAssets
@ -2059,6 +2060,7 @@ func autoConvert_kops_ClusterSpec_To_v1alpha2_ClusterSpec(in *kops.ClusterSpec,
out.KubernetesAPIAccess = in.KubernetesAPIAccess
out.IsolateMasters = in.IsolateMasters
out.UpdatePolicy = in.UpdatePolicy
out.ExternalPolicies = in.ExternalPolicies
out.AdditionalPolicies = in.AdditionalPolicies
if in.FileAssets != nil {
in, out := &in.FileAssets, &out.FileAssets

View File

@ -634,6 +634,25 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
*out = new(string)
**out = **in
}
if in.ExternalPolicies != nil {
in, out := &in.ExternalPolicies, &out.ExternalPolicies
*out = new(map[string][]string)
if **in != nil {
in, out := *in, *out
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
}
if in.AdditionalPolicies != nil {
in, out := &in.AdditionalPolicies, &out.AdditionalPolicies
*out = new(map[string]string)

View File

@ -734,6 +734,25 @@ func (in *ClusterSpec) DeepCopyInto(out *ClusterSpec) {
*out = new(string)
**out = **in
}
if in.ExternalPolicies != nil {
in, out := &in.ExternalPolicies, &out.ExternalPolicies
*out = new(map[string][]string)
if **in != nil {
in, out := *in, *out
*out = make(map[string][]string, len(*in))
for key, val := range *in {
var outVal []string
if val == nil {
(*out)[key] = nil
} else {
in, out := &val, &outVal
*out = make([]string, len(*in))
copy(*out, *in)
}
(*out)[key] = outVal
}
}
}
if in.AdditionalPolicies != nil {
in, out := &in.AdditionalPolicies, &out.AdditionalPolicies
*out = new(map[string]string)

View File

@ -165,6 +165,27 @@ func (b *IAMModelBuilder) buildIAMTasks(igRole kops.InstanceGroupRole, iamName s
c.AddTask(iamInstanceProfileRole)
}
// Create External Policy tasks
{
var externalPolicies []string
if b.Cluster.Spec.ExternalPolicies != nil {
p := *(b.Cluster.Spec.ExternalPolicies)
externalPolicies = append(externalPolicies, p[strings.ToLower(string(igRole))]...)
}
name := fmt.Sprintf("%s-policyoverride", strings.ToLower(string(igRole)))
t := &awstasks.IAMRolePolicy{
Name: s(name),
Lifecycle: b.Lifecycle,
Role: iamRole,
Managed: true,
ExternalPolicies: &externalPolicies,
}
c.AddTask(t)
}
// Generate additional policies if needed, and attach to existing role
{
additionalPolicy := ""

View File

@ -1889,11 +1889,13 @@ func ListRoute53Records(cloud fi.Cloud, clusterName string) ([]*resources.Resour
}
func DeleteIAMRole(cloud fi.Cloud, r *resources.Resource) error {
c := cloud.(awsup.AWSCloud)
var attachedPolicies []*iam.AttachedPolicy
var policyNames []string
c := cloud.(awsup.AWSCloud)
roleName := r.Name
var policyNames []string
// List Inline policies
{
request := &iam.ListRolePoliciesInput{
RoleName: aws.String(roleName),
@ -1914,6 +1916,26 @@ func DeleteIAMRole(cloud fi.Cloud, r *resources.Resource) error {
}
}
// List Attached Policies
{
request := &iam.ListAttachedRolePoliciesInput{
RoleName: aws.String(roleName),
}
err := c.IAM().ListAttachedRolePoliciesPages(request, func(page *iam.ListAttachedRolePoliciesOutput, lastPage bool) bool {
attachedPolicies = append(attachedPolicies, page.AttachedPolicies...)
return true
})
if err != nil {
if awsup.AWSErrorCode(err) == "NoSuchEntity" {
klog.V(2).Infof("Got NoSuchEntity describing IAM RolePolicy %q; will treat as already-detached", roleName)
return nil
}
return fmt.Errorf("error listing IAM role policies for %q: %v", roleName, err)
}
}
// Delete inline policies
for _, policyName := range policyNames {
klog.V(2).Infof("Deleting IAM role policy %q %q", roleName, policyName)
request := &iam.DeleteRolePolicyInput{
@ -1926,6 +1948,20 @@ func DeleteIAMRole(cloud fi.Cloud, r *resources.Resource) error {
}
}
// Detach Managed Policies
for _, policy := range attachedPolicies {
klog.V(2).Infof("Deleting IAM role policy %q %q", roleName, policy)
request := &iam.DetachRolePolicyInput{
RoleName: aws.String(r.Name),
PolicyArn: policy.PolicyArn,
}
_, err := c.IAM().DetachRolePolicy(request)
if err != nil {
return fmt.Errorf("error detaching IAM role policy %q %q: %v", roleName, *policy.PolicyArn, err)
}
}
// Delete Role
{
klog.V(2).Infof("Deleting IAM role %q", r.Name)
request := &iam.DeleteRoleInput{

View File

@ -0,0 +1 @@
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQCtWu40XQo8dczLsCq0OWV+hxm9uV3WxeH9Kgh4sMzQxNtoU1pvW0XdjpkBesRKGoolfWeCLXWxpyQb1IaiMkKoz7MdhQ/6UKjMjP66aFWWp3pwD0uj0HuJ7tq4gKHKRYGTaZIRWpzUiANBrjugVgA+Sd7E/mYwc/DMXkIyRZbvhQ==

View File

@ -0,0 +1,103 @@
apiVersion: kops.k8s.io/v1alpha2
kind: Cluster
metadata:
creationTimestamp: "2016-12-10T22:42:27Z"
name: externalpolicies.example.com
spec:
api:
loadBalancer:
type: Public
additionalSecurityGroups:
- sg-exampleid3
- sg-exampleid4
kubernetesApiAccess:
- 0.0.0.0/0
channel: stable
cloudProvider: aws
cloudLabels:
Owner: John Doe
foo/bar: fib+baz
configBase: memfs://clusters.example.com/externalpolicies.example.com
etcdClusters:
- etcdMembers:
- instanceGroup: master-us-test-1a
name: us-test-1a
name: main
- etcdMembers:
- instanceGroup: master-us-test-1a
name: us-test-1a
name: events
kubeAPIServer:
serviceNodePortRange: 28000-32767
auditWebhookBatchThrottleQps: 3.14
kubernetesVersion: v1.14.0
masterInternalName: api.internal.externalpolicies.example.com
masterPublicName: api.externalpolicies.example.com
networkCIDR: 172.20.0.0/16
networking:
kubenet: {}
nodePortAccess:
- 1.2.3.4/32
- 10.20.30.0/24
nonMasqueradeCIDR: 100.64.0.0/10
sshAccess:
- 0.0.0.0/0
topology:
masters: public
nodes: public
externalPolicies:
node:
- aws:arn:iam:123456789000:policy:test-policy
master:
- aws:arn:iam:123456789000:policy:test-policy
bastion:
- aws:arn:iam:123456789000:policy:test-policy
subnets:
- cidr: 172.20.32.0/19
name: us-test-1a
type: Public
zone: us-test-1a
---
apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
creationTimestamp: "2016-12-10T22:42:28Z"
name: nodes
labels:
kops.k8s.io/cluster: externalpolicies.example.com
spec:
additionalSecurityGroups:
- sg-exampleid3
- sg-exampleid4
associatePublicIp: true
suspendProcesses:
- AZRebalance
image: kope.io/k8s-1.4-debian-jessie-amd64-hvm-ebs-2016-10-21
machineType: t2.medium
maxSize: 2
minSize: 2
role: Node
subnets:
- us-test-1a
detailedInstanceMonitoring: true
---
apiVersion: kops.k8s.io/v1alpha2
kind: InstanceGroup
metadata:
creationTimestamp: "2016-12-10T22:42:28Z"
name: master-us-test-1a
labels:
kops.k8s.io/cluster: externalpolicies.example.com
spec:
associatePublicIp: true
image: kope.io/k8s-1.4-debian-jessie-amd64-hvm-ebs-2016-10-21
machineType: m3.medium
maxSize: 1
minSize: 1
role: Master
subnets:
- us-test-1a

View File

@ -0,0 +1,644 @@
locals = {
cluster_name = "externalpolicies.example.com"
master_autoscaling_group_ids = ["${aws_autoscaling_group.master-us-test-1a-masters-externalpolicies-example-com.id}"]
master_security_group_ids = ["${aws_security_group.masters-externalpolicies-example-com.id}"]
masters_role_arn = "${aws_iam_role.masters-externalpolicies-example-com.arn}"
masters_role_name = "${aws_iam_role.masters-externalpolicies-example-com.name}"
node_autoscaling_group_ids = ["${aws_autoscaling_group.nodes-externalpolicies-example-com.id}"]
node_security_group_ids = ["${aws_security_group.nodes-externalpolicies-example-com.id}", "sg-exampleid3", "sg-exampleid4"]
node_subnet_ids = ["${aws_subnet.us-test-1a-externalpolicies-example-com.id}"]
nodes_role_arn = "${aws_iam_role.nodes-externalpolicies-example-com.arn}"
nodes_role_name = "${aws_iam_role.nodes-externalpolicies-example-com.name}"
region = "us-test-1"
route_table_public_id = "${aws_route_table.externalpolicies-example-com.id}"
subnet_us-test-1a_id = "${aws_subnet.us-test-1a-externalpolicies-example-com.id}"
vpc_cidr_block = "${aws_vpc.externalpolicies-example-com.cidr_block}"
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
}
output "cluster_name" {
value = "externalpolicies.example.com"
}
output "master_autoscaling_group_ids" {
value = ["${aws_autoscaling_group.master-us-test-1a-masters-externalpolicies-example-com.id}"]
}
output "master_security_group_ids" {
value = ["${aws_security_group.masters-externalpolicies-example-com.id}"]
}
output "masters_role_arn" {
value = "${aws_iam_role.masters-externalpolicies-example-com.arn}"
}
output "masters_role_name" {
value = "${aws_iam_role.masters-externalpolicies-example-com.name}"
}
output "node_autoscaling_group_ids" {
value = ["${aws_autoscaling_group.nodes-externalpolicies-example-com.id}"]
}
output "node_security_group_ids" {
value = ["${aws_security_group.nodes-externalpolicies-example-com.id}", "sg-exampleid3", "sg-exampleid4"]
}
output "node_subnet_ids" {
value = ["${aws_subnet.us-test-1a-externalpolicies-example-com.id}"]
}
output "nodes_role_arn" {
value = "${aws_iam_role.nodes-externalpolicies-example-com.arn}"
}
output "nodes_role_name" {
value = "${aws_iam_role.nodes-externalpolicies-example-com.name}"
}
output "region" {
value = "us-test-1"
}
output "route_table_public_id" {
value = "${aws_route_table.externalpolicies-example-com.id}"
}
output "subnet_us-test-1a_id" {
value = "${aws_subnet.us-test-1a-externalpolicies-example-com.id}"
}
output "vpc_cidr_block" {
value = "${aws_vpc.externalpolicies-example-com.cidr_block}"
}
output "vpc_id" {
value = "${aws_vpc.externalpolicies-example-com.id}"
}
provider "aws" {
region = "us-test-1"
}
resource "aws_autoscaling_attachment" "master-us-test-1a-masters-externalpolicies-example-com" {
elb = "${aws_elb.api-externalpolicies-example-com.id}"
autoscaling_group_name = "${aws_autoscaling_group.master-us-test-1a-masters-externalpolicies-example-com.id}"
}
resource "aws_autoscaling_group" "master-us-test-1a-masters-externalpolicies-example-com" {
name = "master-us-test-1a.masters.externalpolicies.example.com"
launch_configuration = "${aws_launch_configuration.master-us-test-1a-masters-externalpolicies-example-com.id}"
max_size = 1
min_size = 1
vpc_zone_identifier = ["${aws_subnet.us-test-1a-externalpolicies-example-com.id}"]
tag = {
key = "KubernetesCluster"
value = "externalpolicies.example.com"
propagate_at_launch = true
}
tag = {
key = "Name"
value = "master-us-test-1a.masters.externalpolicies.example.com"
propagate_at_launch = true
}
tag = {
key = "Owner"
value = "John Doe"
propagate_at_launch = true
}
tag = {
key = "foo/bar"
value = "fib+baz"
propagate_at_launch = true
}
tag = {
key = "k8s.io/role/master"
value = "1"
propagate_at_launch = true
}
tag = {
key = "kops.k8s.io/instancegroup"
value = "master-us-test-1a"
propagate_at_launch = true
}
metrics_granularity = "1Minute"
enabled_metrics = ["GroupDesiredCapacity", "GroupInServiceInstances", "GroupMaxSize", "GroupMinSize", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"]
}
resource "aws_autoscaling_group" "nodes-externalpolicies-example-com" {
name = "nodes.externalpolicies.example.com"
launch_configuration = "${aws_launch_configuration.nodes-externalpolicies-example-com.id}"
max_size = 2
min_size = 2
vpc_zone_identifier = ["${aws_subnet.us-test-1a-externalpolicies-example-com.id}"]
tag = {
key = "KubernetesCluster"
value = "externalpolicies.example.com"
propagate_at_launch = true
}
tag = {
key = "Name"
value = "nodes.externalpolicies.example.com"
propagate_at_launch = true
}
tag = {
key = "Owner"
value = "John Doe"
propagate_at_launch = true
}
tag = {
key = "foo/bar"
value = "fib+baz"
propagate_at_launch = true
}
tag = {
key = "k8s.io/role/node"
value = "1"
propagate_at_launch = true
}
tag = {
key = "kops.k8s.io/instancegroup"
value = "nodes"
propagate_at_launch = true
}
metrics_granularity = "1Minute"
enabled_metrics = ["GroupDesiredCapacity", "GroupInServiceInstances", "GroupMaxSize", "GroupMinSize", "GroupPendingInstances", "GroupStandbyInstances", "GroupTerminatingInstances", "GroupTotalInstances"]
suspended_processes = ["AZRebalance"]
}
resource "aws_ebs_volume" "us-test-1a-etcd-events-externalpolicies-example-com" {
availability_zone = "us-test-1a"
size = 20
type = "gp2"
encrypted = false
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "us-test-1a.etcd-events.externalpolicies.example.com"
Owner = "John Doe"
"foo/bar" = "fib+baz"
"k8s.io/etcd/events" = "us-test-1a/us-test-1a"
"k8s.io/role/master" = "1"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_ebs_volume" "us-test-1a-etcd-main-externalpolicies-example-com" {
availability_zone = "us-test-1a"
size = 20
type = "gp2"
encrypted = false
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "us-test-1a.etcd-main.externalpolicies.example.com"
Owner = "John Doe"
"foo/bar" = "fib+baz"
"k8s.io/etcd/main" = "us-test-1a/us-test-1a"
"k8s.io/role/master" = "1"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_elb" "api-externalpolicies-example-com" {
name = "api-externalpolicies-exam-5cse45"
listener = {
instance_port = 443
instance_protocol = "TCP"
lb_port = 443
lb_protocol = "TCP"
}
security_groups = ["${aws_security_group.api-elb-externalpolicies-example-com.id}", "sg-exampleid3", "sg-exampleid4"]
subnets = ["${aws_subnet.us-test-1a-externalpolicies-example-com.id}"]
health_check = {
target = "SSL:443"
healthy_threshold = 2
unhealthy_threshold = 2
interval = 10
timeout = 5
}
cross_zone_load_balancing = false
idle_timeout = 300
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "api.externalpolicies.example.com"
Owner = "John Doe"
"foo/bar" = "fib+baz"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_iam_instance_profile" "masters-externalpolicies-example-com" {
name = "masters.externalpolicies.example.com"
role = "${aws_iam_role.masters-externalpolicies-example-com.name}"
}
resource "aws_iam_instance_profile" "nodes-externalpolicies-example-com" {
name = "nodes.externalpolicies.example.com"
role = "${aws_iam_role.nodes-externalpolicies-example-com.name}"
}
resource "aws_iam_role" "masters-externalpolicies-example-com" {
name = "masters.externalpolicies.example.com"
assume_role_policy = "${file("${path.module}/data/aws_iam_role_masters.externalpolicies.example.com_policy")}"
}
resource "aws_iam_role" "nodes-externalpolicies-example-com" {
name = "nodes.externalpolicies.example.com"
assume_role_policy = "${file("${path.module}/data/aws_iam_role_nodes.externalpolicies.example.com_policy")}"
}
resource "aws_iam_role_policy" "masters-externalpolicies-example-com" {
name = "masters.externalpolicies.example.com"
role = "${aws_iam_role.masters-externalpolicies-example-com.name}"
policy = "${file("${path.module}/data/aws_iam_role_policy_masters.externalpolicies.example.com_policy")}"
}
resource "aws_iam_role_policy" "nodes-externalpolicies-example-com" {
name = "nodes.externalpolicies.example.com"
role = "${aws_iam_role.nodes-externalpolicies-example-com.name}"
policy = "${file("${path.module}/data/aws_iam_role_policy_nodes.externalpolicies.example.com_policy")}"
}
resource "aws_iam_role_policy_attachment" "master-policyoverride-1178482798" {
role = "${aws_iam_role.masters-externalpolicies-example-com.name}"
policy_arn = "aws:arn:iam:123456789000:policy:test-policy"
}
resource "aws_iam_role_policy_attachment" "node-policyoverride-1178482798" {
role = "${aws_iam_role.nodes-externalpolicies-example-com.name}"
policy_arn = "aws:arn:iam:123456789000:policy:test-policy"
}
resource "aws_internet_gateway" "externalpolicies-example-com" {
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "externalpolicies.example.com"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_key_pair" "kubernetes-externalpolicies-example-com-c4a6ed9aa889b9e2c39cd663eb9c7157" {
key_name = "kubernetes.externalpolicies.example.com-c4:a6:ed:9a:a8:89:b9:e2:c3:9c:d6:63:eb:9c:71:57"
public_key = "${file("${path.module}/data/aws_key_pair_kubernetes.externalpolicies.example.com-c4a6ed9aa889b9e2c39cd663eb9c7157_public_key")}"
}
resource "aws_launch_configuration" "master-us-test-1a-masters-externalpolicies-example-com" {
name_prefix = "master-us-test-1a.masters.externalpolicies.example.com-"
image_id = "ami-12345678"
instance_type = "m3.medium"
key_name = "${aws_key_pair.kubernetes-externalpolicies-example-com-c4a6ed9aa889b9e2c39cd663eb9c7157.id}"
iam_instance_profile = "${aws_iam_instance_profile.masters-externalpolicies-example-com.id}"
security_groups = ["${aws_security_group.masters-externalpolicies-example-com.id}"]
associate_public_ip_address = true
user_data = "${file("${path.module}/data/aws_launch_configuration_master-us-test-1a.masters.externalpolicies.example.com_user_data")}"
root_block_device = {
volume_type = "gp2"
volume_size = 64
delete_on_termination = true
}
ephemeral_block_device = {
device_name = "/dev/sdc"
virtual_name = "ephemeral0"
}
lifecycle = {
create_before_destroy = true
}
enable_monitoring = false
}
resource "aws_launch_configuration" "nodes-externalpolicies-example-com" {
name_prefix = "nodes.externalpolicies.example.com-"
image_id = "ami-12345678"
instance_type = "t2.medium"
key_name = "${aws_key_pair.kubernetes-externalpolicies-example-com-c4a6ed9aa889b9e2c39cd663eb9c7157.id}"
iam_instance_profile = "${aws_iam_instance_profile.nodes-externalpolicies-example-com.id}"
security_groups = ["${aws_security_group.nodes-externalpolicies-example-com.id}", "sg-exampleid3", "sg-exampleid4"]
associate_public_ip_address = true
user_data = "${file("${path.module}/data/aws_launch_configuration_nodes.externalpolicies.example.com_user_data")}"
root_block_device = {
volume_type = "gp2"
volume_size = 128
delete_on_termination = true
}
lifecycle = {
create_before_destroy = true
}
enable_monitoring = true
}
resource "aws_route" "route-0-0-0-0--0" {
route_table_id = "${aws_route_table.externalpolicies-example-com.id}"
destination_cidr_block = "0.0.0.0/0"
gateway_id = "${aws_internet_gateway.externalpolicies-example-com.id}"
}
resource "aws_route53_record" "api-externalpolicies-example-com" {
name = "api.externalpolicies.example.com"
type = "A"
alias = {
name = "${aws_elb.api-externalpolicies-example-com.dns_name}"
zone_id = "${aws_elb.api-externalpolicies-example-com.zone_id}"
evaluate_target_health = false
}
zone_id = "/hostedzone/Z1AFAKE1ZON3YO"
}
resource "aws_route_table" "externalpolicies-example-com" {
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "externalpolicies.example.com"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
"kubernetes.io/kops/role" = "public"
}
}
resource "aws_route_table_association" "us-test-1a-externalpolicies-example-com" {
subnet_id = "${aws_subnet.us-test-1a-externalpolicies-example-com.id}"
route_table_id = "${aws_route_table.externalpolicies-example-com.id}"
}
resource "aws_security_group" "api-elb-externalpolicies-example-com" {
name = "api-elb.externalpolicies.example.com"
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
description = "Security group for api ELB"
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "api-elb.externalpolicies.example.com"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_security_group" "masters-externalpolicies-example-com" {
name = "masters.externalpolicies.example.com"
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
description = "Security group for masters"
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "masters.externalpolicies.example.com"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_security_group" "nodes-externalpolicies-example-com" {
name = "nodes.externalpolicies.example.com"
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
description = "Security group for nodes"
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "nodes.externalpolicies.example.com"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_security_group_rule" "all-master-to-master" {
type = "ingress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
from_port = 0
to_port = 0
protocol = "-1"
}
resource "aws_security_group_rule" "all-master-to-node" {
type = "ingress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
from_port = 0
to_port = 0
protocol = "-1"
}
resource "aws_security_group_rule" "all-node-to-node" {
type = "ingress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 0
to_port = 0
protocol = "-1"
}
resource "aws_security_group_rule" "api-elb-egress" {
type = "egress"
security_group_id = "${aws_security_group.api-elb-externalpolicies-example-com.id}"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "https-api-elb-0-0-0-0--0" {
type = "ingress"
security_group_id = "${aws_security_group.api-elb-externalpolicies-example-com.id}"
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "https-elb-to-master" {
type = "ingress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.api-elb-externalpolicies-example-com.id}"
from_port = 443
to_port = 443
protocol = "tcp"
}
resource "aws_security_group_rule" "icmp-pmtu-api-elb-0-0-0-0--0" {
type = "ingress"
security_group_id = "${aws_security_group.api-elb-externalpolicies-example-com.id}"
from_port = 3
to_port = 4
protocol = "icmp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "master-egress" {
type = "egress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "node-egress" {
type = "egress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "node-to-master-tcp-1-2379" {
type = "ingress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 1
to_port = 2379
protocol = "tcp"
}
resource "aws_security_group_rule" "node-to-master-tcp-2382-4000" {
type = "ingress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 2382
to_port = 4000
protocol = "tcp"
}
resource "aws_security_group_rule" "node-to-master-tcp-4003-65535" {
type = "ingress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 4003
to_port = 65535
protocol = "tcp"
}
resource "aws_security_group_rule" "node-to-master-udp-1-65535" {
type = "ingress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
source_security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 1
to_port = 65535
protocol = "udp"
}
resource "aws_security_group_rule" "nodeport-tcp-external-to-node-1-2-3-4--32" {
type = "ingress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 28000
to_port = 32767
protocol = "tcp"
cidr_blocks = ["1.2.3.4/32"]
}
resource "aws_security_group_rule" "nodeport-tcp-external-to-node-10-20-30-0--24" {
type = "ingress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 28000
to_port = 32767
protocol = "tcp"
cidr_blocks = ["10.20.30.0/24"]
}
resource "aws_security_group_rule" "nodeport-udp-external-to-node-1-2-3-4--32" {
type = "ingress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 28000
to_port = 32767
protocol = "udp"
cidr_blocks = ["1.2.3.4/32"]
}
resource "aws_security_group_rule" "nodeport-udp-external-to-node-10-20-30-0--24" {
type = "ingress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 28000
to_port = 32767
protocol = "udp"
cidr_blocks = ["10.20.30.0/24"]
}
resource "aws_security_group_rule" "ssh-external-to-master-0-0-0-0--0" {
type = "ingress"
security_group_id = "${aws_security_group.masters-externalpolicies-example-com.id}"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_security_group_rule" "ssh-external-to-node-0-0-0-0--0" {
type = "ingress"
security_group_id = "${aws_security_group.nodes-externalpolicies-example-com.id}"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
resource "aws_subnet" "us-test-1a-externalpolicies-example-com" {
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
cidr_block = "172.20.32.0/19"
availability_zone = "us-test-1a"
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "us-test-1a.externalpolicies.example.com"
SubnetType = "Public"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
"kubernetes.io/role/elb" = "1"
}
}
resource "aws_vpc" "externalpolicies-example-com" {
cidr_block = "172.20.0.0/16"
enable_dns_hostnames = true
enable_dns_support = true
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "externalpolicies.example.com"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_vpc_dhcp_options" "externalpolicies-example-com" {
domain_name = "us-test-1.compute.internal"
domain_name_servers = ["AmazonProvidedDNS"]
tags = {
KubernetesCluster = "externalpolicies.example.com"
Name = "externalpolicies.example.com"
"kubernetes.io/cluster/externalpolicies.example.com" = "owned"
}
}
resource "aws_vpc_dhcp_options_association" "externalpolicies-example-com" {
vpc_id = "${aws_vpc.externalpolicies-example-com.id}"
dhcp_options_id = "${aws_vpc_dhcp_options.externalpolicies-example-com.id}"
}
terraform = {
required_version = ">= 0.9.3"
}

View File

@ -18,6 +18,7 @@ package awstasks
import (
"fmt"
"hash/fnv"
"encoding/json"
"net/url"
@ -44,11 +45,49 @@ type IAMRolePolicy struct {
// The PolicyDocument to create as an inline policy.
// If the PolicyDocument is empty, the policy will be removed.
PolicyDocument fi.Resource
// External (non-kops managed) AWS policies to attach to the role
ExternalPolicies *[]string
// Managed tracks the use of ExternalPolicies
Managed bool
}
func (e *IAMRolePolicy) Find(c *fi.Context) (*IAMRolePolicy, error) {
var actual IAMRolePolicy
cloud := c.Cloud.(awsup.AWSCloud)
// Handle policy overrides
if e.ExternalPolicies != nil {
request := &iam.ListAttachedRolePoliciesInput{
RoleName: e.Role.Name,
}
response, err := cloud.IAM().ListAttachedRolePolicies(request)
if awsErr, ok := err.(awserr.Error); ok {
if awsErr.Code() == "NoSuchEntity" {
return nil, nil
}
return nil, fmt.Errorf("error getting policies for role: %v", err)
}
var policies []string
if response != nil && len(response.AttachedPolicies) > 0 {
for _, policy := range response.AttachedPolicies {
policies = append(policies, aws.StringValue(policy.PolicyArn))
}
}
actual.ID = e.ID
actual.Name = e.Name
actual.Lifecycle = e.Lifecycle
actual.Role = e.Role
actual.Managed = true
actual.ExternalPolicies = &policies
return &actual, nil
}
request := &iam.GetRolePolicyInput{
RoleName: e.Role.Name,
PolicyName: e.Name,
@ -65,7 +104,6 @@ func (e *IAMRolePolicy) Find(c *fi.Context) (*IAMRolePolicy, error) {
}
p := response
actual := &IAMRolePolicy{}
actual.Role = &IAMRole{Name: p.RoleName}
if aws.StringValue(e.Role.Name) == aws.StringValue(p.RoleName) {
actual.Role.ID = e.Role.ID
@ -87,7 +125,7 @@ func (e *IAMRolePolicy) Find(c *fi.Context) (*IAMRolePolicy, error) {
// Avoid spurious changes
actual.Lifecycle = e.Lifecycle
return actual, nil
return &actual, nil
}
func (e *IAMRolePolicy) Run(c *fi.Context) error {
@ -109,9 +147,10 @@ func (_ *IAMRolePolicy) ShouldCreate(a, e, changes *IAMRolePolicy) (bool, error)
return false, fmt.Errorf("error rendering PolicyDocument: %v", err)
}
if a == nil && ePolicy == "" {
if a == nil && ePolicy == "" && e.ExternalPolicies == nil {
return false, nil
}
return true, nil
}
@ -121,6 +160,55 @@ func (_ *IAMRolePolicy) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *IAMRoleP
return fmt.Errorf("error rendering PolicyDocument: %v", err)
}
// Handles the full lifecycle of Policy Overrides
if e.Managed {
// Attach policies that are not already attached
AttachPolicies:
for _, policy := range *e.ExternalPolicies {
for _, cloudPolicy := range *a.ExternalPolicies {
if cloudPolicy == policy {
continue AttachPolicies
}
}
request := &iam.AttachRolePolicyInput{
RoleName: e.Role.Name,
PolicyArn: s(policy),
}
_, err = t.Cloud.IAM().AttachRolePolicy(request)
if err != nil {
return fmt.Errorf("error attaching IAMRolePolicy: %v", err)
}
}
// Clean up unused cloud policies
CheckPolicies:
for _, cloudPolicy := range *a.ExternalPolicies {
for _, policy := range *e.ExternalPolicies {
if policy == cloudPolicy {
continue CheckPolicies
}
}
klog.V(2).Infof("Detaching unused IAMRolePolicy %s/%s", aws.StringValue(e.Role.Name), cloudPolicy)
// Detach policy
request := &iam.DetachRolePolicyInput{
RoleName: e.Role.Name,
PolicyArn: s(cloudPolicy),
}
_, err := t.Cloud.IAM().DetachRolePolicy(request)
if err != nil {
klog.V(2).Infof("Unable to detach IAMRolePolicy %s/%s", aws.StringValue(e.Role.Name), cloudPolicy)
return err
}
}
return nil
}
if policy == "" {
// A deletion
@ -192,12 +280,33 @@ func (e *IAMRolePolicy) policyDocumentString() (string, error) {
}
type terraformIAMRolePolicy struct {
Name *string `json:"name"`
Name *string `json:"name,omitempty"`
Role *terraform.Literal `json:"role"`
PolicyDocument *terraform.Literal `json:"policy"`
PolicyDocument *terraform.Literal `json:"policy,omitempty"`
PolicyArn *string `json:"policy_arn,omitempty"`
}
func (_ *IAMRolePolicy) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *IAMRolePolicy) error {
if e.ExternalPolicies != nil && len(*e.ExternalPolicies) > 0 {
for _, policy := range *e.ExternalPolicies {
// create a hash of the arn
h := fnv.New32a()
h.Write([]byte(policy))
name := fmt.Sprintf("%s-%d", *e.Name, h.Sum32())
tf := &terraformIAMRolePolicy{
Role: e.Role.TerraformLink(),
PolicyArn: s(policy),
}
err := t.RenderResource("aws_iam_role_policy_attachment", name, tf)
if err != nil {
return fmt.Errorf("error rendering RolePolicyAttachment: %v", err)
}
}
}
policyString, err := e.policyDocumentString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)
@ -233,6 +342,12 @@ type cloudformationIAMRolePolicy struct {
}
func (_ *IAMRolePolicy) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *IAMRolePolicy) error {
// Currently CloudFormation does not have a reciprocal function to Terraform that allows the modification of a role
// after the fact. In order to make this feature complete we would have to intercept the role task and modify it.
if e.ExternalPolicies != nil && len(*e.ExternalPolicies) > 0 {
return fmt.Errorf("CloudFormation not supported for use with ExternalPolicies.")
}
policyString, err := e.policyDocumentString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)