Add support for extensible IAM permissions

This commit is contained in:
Yissachar Radcliffe 2016-12-15 13:20:33 -05:00
parent b04cd5a9f5
commit 13ac2d49d3
6 changed files with 240 additions and 0 deletions

103
docs/iam_roles.md Normal file
View File

@ -0,0 +1,103 @@
# IAM Roles
Two IAM roles are created for the cluster: one for the masters, and one for the nodes.
The permissions are kept to the minimum required to setup and maintain the cluster.
Master permissions:
```
ec2:*
route53:*
elasticloadbalancing:*
ecr:GetAuthorizationToken
ecr:BatchCheckLayerAvailability
ecr:GetDownloadUrlForLayer
ecr:GetRepositoryPolicy
ecr:DescribeRepositories
ecr:ListImages
ecr:BatchGetImage
// The following permissions are only created if you are using etcd volumes with "encrypted: true" and a custom kmsKeyId.
// They are scoped to the kmsKeyId that you are using.
kms:Encrypt
kms:Decrypt
kms:ReEncrypt*
kms:GenerateDataKey*
kms:DescribeKey
kms:CreateGrant
kms:ListGrants
kms:RevokeGrant
```
Node permissions:
```
ec2:Describe*
route53:*
ecr:GetAuthorizationToken
ecr:BatchCheckLayerAvailability
ecr:GetDownloadUrlForLayer
ecr:GetRepositoryPolicy
ecr:DescribeRepositories
ecr:ListImages
ecr:BatchGetImage
```
## Adding Additional Permissions
Sometimes you may need to extend the kops IAM roles to add additional permissions. You can do this
through the `additionalMasterPermissions` and `additionalNodePermissions` spec fields. For instance, let's say you want
to add DynamoDB and Elasicsearch permissions to your nodes.
Edit your cluster via `kops edit cluster ${CLUSTER_NAME}` and add the following to the spec:
```
additionalNodePermissions: |
[
{
"Effect": "Allow",
"Action": ["dynamodb:*"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["es:*"],
"Resource": ["*"]
}
]
```
After you're finished editing, your cluster spec should look something like this:
```
metadata:
creationTimestamp: "2016-06-27T14:23:34Z"
name: ${CLUSTER_NAME}
spec:
cloudProvider: aws
networkCIDR: 10.100.0.0/16
networkID: vpc-a80734c1
nonMasqueradeCIDR: 100.64.0.0/10
zones:
- cidr: 10.100.32.0/19
name: eu-central-1a
additionalNodePermissions: |
[
{
"Effect": "Allow",
"Action": ["dynamodb:*"],
"Resource": ["*"]
},
{
"Effect": "Allow",
"Action": ["es:*"],
"Resource": ["*"]
}
]
```
Now you can update to have the changes take effect:
```
kops update cluster ${CLUSTER_NAME} --yes
```

View File

@ -135,6 +135,11 @@ type ClusterSpec struct {
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// Additional permissions to add to the master role
AdditionalMasterPermissions *string `json:"additionalMasterPermissions,omitempty"`
// Additional permissions to add to the node role
AdditionalNodePermissions *string `json:"additionalNodePermissions,omitempty"`
//HairpinMode string `json:",omitempty"`
//
//OpencontrailTag string `json:",omitempty"`

View File

@ -134,6 +134,11 @@ type ClusterSpec struct {
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// Additional permissions to add to the master role
AdditionalMasterPermissions *string `json:"additionalMasterPermissions,omitempty"`
// Additional permissions to add to the node role
AdditionalNodePermissions *string `json:"additionalNodePermissions,omitempty"`
//HairpinMode string `json:",omitempty"`
//
//OpencontrailTag string `json:",omitempty"`

View File

@ -134,6 +134,11 @@ type ClusterSpec struct {
// missing: default policy (currently OS security upgrades that do not require a reboot)
UpdatePolicy *string `json:"updatePolicy,omitempty"`
// Additional permissions to add to the master role
AdditionalMasterPermissions *string `json:"additionalMasterPermissions,omitempty"`
// Additional permissions to add to the node role
AdditionalNodePermissions *string `json:"additionalNodePermissions,omitempty"`
// EtcdClusters stores the configuration for each cluster
EtcdClusters []*EtcdClusterSpec `json:"etcdClusters,omitempty"`

View File

@ -100,6 +100,12 @@ func (b *IAMPolicyBuilder) BuildAWSIAMPolicy() (*IAMPolicy, error) {
Action: []string{"route53:*"},
Resource: []string{"*"},
})
if b.Cluster.Spec.AdditionalNodePermissions != nil {
additionalNodePermissions := make([]*IAMStatement, 0)
json.Unmarshal([]byte(*b.Cluster.Spec.AdditionalNodePermissions), &additionalNodePermissions)
p.Statement = append(p.Statement, additionalNodePermissions...)
}
}
{
@ -166,6 +172,12 @@ func (b *IAMPolicyBuilder) BuildAWSIAMPolicy() (*IAMPolicy, error) {
Resource: kmsKeyIDs.List(),
})
}
if b.Cluster.Spec.AdditionalMasterPermissions != nil {
additionalMasterPermissions := make([]*IAMStatement, 0)
json.Unmarshal([]byte(*b.Cluster.Spec.AdditionalMasterPermissions), &additionalMasterPermissions)
p.Statement = append(p.Statement, additionalMasterPermissions...)
}
}
// For S3 IAM permissions, we grant permissions to subtrees. So find the parents;

View File

@ -0,0 +1,110 @@
/*
Copyright 2016 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.
*/
package cloudup
import (
api "k8s.io/kops/pkg/apis/kops"
"testing"
"strings"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/pkg/model/iam"
"regexp"
"log"
"fmt"
)
func TestBuildIAMPolicy_AdditionalMasterPermissions(t *testing.T) {
c := buildCluster(nil)
additionalMasterPermissions := `{
"Effect": "Allow",
"Action": ["dynamodb:*"],
"Resource": ["*"]
}`
c.Spec.AdditionalMasterPermissions = fi.String(fmt.Sprintf("[%s]", additionalMasterPermissions))
iamPolicyBuilder := &iam.IAMPolicyBuilder{
Cluster: c,
Role: api.InstanceGroupRoleMaster,
Region: "us-east-1",
}
iamPolicy, err := iamPolicyBuilder.BuildAWSIAMPolicy()
if err != nil {
t.Fatalf("BuildAWSIAMPolicy error: %v", err)
}
json, err := iamPolicy.AsJSON()
if err != nil {
t.Fatalf("Error marshaling IAM policy: %v", err)
}
reg, err := regexp.Compile("\\s")
if err != nil {
log.Fatal(err)
}
jsonWhitespaceLess := reg.ReplaceAllString(json, "")
additionalMasterWhiteSpaceLess := reg.ReplaceAllString(additionalMasterPermissions, "")
if !strings.Contains(jsonWhitespaceLess, additionalMasterWhiteSpaceLess) {
t.Fatalf("IAM Policy did not contain additionalMasterPermissions: %v and %v", jsonWhitespaceLess, additionalMasterWhiteSpaceLess)
}
}
func TestBuildIAMPolicy_AdditionalNodePermissions(t *testing.T) {
c := buildCluster(nil)
additionalNodePermissions := `{
"Effect": "Allow",
"Action": ["es:*"],
"Resource": ["*"]
}`
c.Spec.AdditionalNodePermissions = fi.String(fmt.Sprintf("[%s]", additionalNodePermissions))
iamPolicyBuilder := &iam.IAMPolicyBuilder{
Cluster: c,
Role: api.InstanceGroupRoleNode,
Region: "us-west-2",
}
iamPolicy, err := iamPolicyBuilder.BuildAWSIAMPolicy()
if err != nil {
t.Fatalf("BuildAWSIAMPolicy error: %v", err)
}
json, err := iamPolicy.AsJSON()
if err != nil {
t.Fatalf("Error marshaling IAM policy: %v", err)
}
reg, err := regexp.Compile("\\s")
if err != nil {
log.Fatal(err)
}
jsonWhitespaceLess := reg.ReplaceAllString(json, "")
additionalNodeWhiteSpaceLess := reg.ReplaceAllString(additionalNodePermissions, "")
if !strings.Contains(jsonWhitespaceLess, additionalNodeWhiteSpaceLess) {
t.Fatalf("IAM Policy did not contain additionalNodePermissions: %v and %v", jsonWhitespaceLess, additionalNodeWhiteSpaceLess)
}
}