Merge pull request #2449 from justinsb/fix_automatic_hosted_zone_creation

Fix automatic private DNS zone creation
This commit is contained in:
Chris Love 2017-04-30 22:57:28 -06:00 committed by GitHub
commit dd824cb679
6 changed files with 131 additions and 83 deletions

View File

@ -38,7 +38,6 @@ type KopsModelContext struct {
Cluster *kops.Cluster
Region string
HostedZoneID string // used to set up route53 IAM policy
InstanceGroups []*kops.InstanceGroup
SSHPublicKeys [][]byte

View File

@ -19,6 +19,7 @@ package model
import (
"fmt"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/dns"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
"strings"
@ -72,6 +73,15 @@ func (b *DNSModelBuilder) Build(c *fi.ModelBuilderContext) error {
if err := b.ensureDNSZone(c); err != nil {
return err
}
} else {
// We now create the DNS Zone for AWS even in the case of public zones;
// it has to exist for the IAM record anyway.
// TODO: We can now rationalize the code paths
if !dns.IsGossipHostname(b.Cluster.Name) {
if err := b.ensureDNSZone(c); err != nil {
return err
}
}
}
if b.UseLoadBalancerForAPI() {

View File

@ -19,6 +19,7 @@ package model
import (
"encoding/json"
"fmt"
"github.com/golang/glog"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/model/iam"
"k8s.io/kops/upup/pkg/fi"
@ -80,15 +81,30 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
}
policy, err := b.buildAWSIAMPolicy(role)
if err != nil {
return err
}
{
iamPolicy := &iam.IAMPolicyResource{
Builder: &iam.IAMPolicyBuilder{
Cluster: b.Cluster,
Role: role,
Region: b.Region,
},
}
// This is slightly tricky; we need to know the hosted zone id,
// but we might be creating the hosted zone dynamically.
// TODO: I don't love this technique for finding the task by name & modifying it
dnsZoneTask, found := c.Tasks["DNSZone/"+b.NameForDNSZone()]
if found {
iamPolicy.DNSZone = dnsZoneTask.(*awstasks.DNSZone)
} else {
glog.V(2).Infof("Task %q not found; won't set route53 permissions in IAM", "DNSZone/"+b.NameForDNSZone())
}
t := &awstasks.IAMRolePolicy{
Name: s(name),
Role: iamRole,
PolicyDocument: fi.WrapResource(fi.NewStringResource(policy)),
PolicyDocument: iamPolicy,
}
c.AddTask(t)
}
@ -154,26 +170,6 @@ func (b *IAMModelBuilder) Build(c *fi.ModelBuilderContext) error {
return nil
}
// buildAWSIAMPolicy produces the AWS IAM policy for the given role
func (b *IAMModelBuilder) buildAWSIAMPolicy(role kops.InstanceGroupRole) (string, error) {
pb := &iam.IAMPolicyBuilder{
Cluster: b.Cluster,
Role: role,
Region: b.Region,
HostedZoneID: b.HostedZoneID,
}
policy, err := pb.BuildAWSIAMPolicy()
if err != nil {
return "", fmt.Errorf("error building IAM policy: %v", err)
}
json, err := policy.AsJSON()
if err != nil {
return "", fmt.Errorf("error building IAM policy: %v", err)
}
return json, nil
}
// buildAWSIAMRolePolicy produces the AWS IAM role policy for the given role
func (b *IAMModelBuilder) buildAWSIAMRolePolicy() (fi.Resource, error) {
functions := template.FuncMap{

View File

@ -17,14 +17,18 @@ limitations under the License.
package iam
import (
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/util/sets"
api "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/util/stringorslice"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
"k8s.io/kops/util/pkg/vfs"
)
@ -175,25 +179,9 @@ func (b *IAMPolicyBuilder) BuildAWSIAMPolicy() (*IAMPolicy, error) {
}
}
p.Statement = append(p.Statement, &IAMStatement{
Effect: IAMStatementEffectAllow,
Action: stringorslice.Of("route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets",
"route53:GetHostedZone"),
Resource: stringorslice.Slice([]string{"arn:aws:route53:::hostedzone/" + b.HostedZoneID}),
})
p.Statement = append(p.Statement, &IAMStatement{
Effect: IAMStatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:GetChange"}),
Resource: stringorslice.Slice([]string{"arn:aws:route53:::change/*"}),
})
p.Statement = append(p.Statement, &IAMStatement{
Effect: IAMStatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:ListHostedZones"}),
Resource: wildcard,
})
if b.HostedZoneID != "" {
addRoute53Permissions(p, b.HostedZoneID)
}
// For S3 IAM permissions, we grant permissions to subtrees. So find the parents;
// we don't need to grant mypath and mypath/child.
@ -273,6 +261,33 @@ func (b *IAMPolicyBuilder) BuildAWSIAMPolicy() (*IAMPolicy, error) {
return p, nil
}
func addRoute53Permissions(p *IAMPolicy, hostedZoneID string) {
// Remove /hostedzone/ prefix (if present)
hostedZoneID = strings.TrimPrefix(hostedZoneID, "/")
hostedZoneID = strings.TrimPrefix(hostedZoneID, "hostedzone/")
p.Statement = append(p.Statement, &IAMStatement{
Effect: IAMStatementEffectAllow,
Action: stringorslice.Of("route53:ChangeResourceRecordSets",
"route53:ListResourceRecordSets",
"route53:GetHostedZone"),
Resource: stringorslice.Slice([]string{"arn:aws:route53:::hostedzone/" + hostedZoneID}),
})
p.Statement = append(p.Statement, &IAMStatement{
Effect: IAMStatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:GetChange"}),
Resource: stringorslice.Slice([]string{"arn:aws:route53:::change/*"}),
})
wildcard := stringorslice.Slice([]string{"*"})
p.Statement = append(p.Statement, &IAMStatement{
Effect: IAMStatementEffectAllow,
Action: stringorslice.Slice([]string{"route53:ListHostedZones"}),
Resource: wildcard,
})
}
// IAMPrefix returns the prefix for AWS ARNs in the current region, for use with IAM
// it is arn:aws everywhere but in cn-north, where it is arn:aws-cn
func (b *IAMPolicyBuilder) IAMPrefix() string {
@ -283,3 +298,40 @@ func (b *IAMPolicyBuilder) IAMPrefix() string {
return "arn:aws"
}
}
type IAMPolicyResource struct {
Builder *IAMPolicyBuilder
DNSZone *awstasks.DNSZone
}
var _ fi.Resource = &IAMPolicyResource{}
var _ fi.HasDependencies = &IAMPolicyResource{}
func (b *IAMPolicyResource) GetDependencies(tasks map[string]fi.Task) []fi.Task {
return []fi.Task{b.DNSZone}
}
// Open produces the AWS IAM policy for the given role
func (b *IAMPolicyResource) Open() (io.Reader, error) {
// Defensive copy before mutation
pb := *b.Builder
if b.DNSZone != nil {
hostedZoneID := fi.StringValue(b.DNSZone.ZoneID)
if hostedZoneID == "" {
// Dependency analysis failure?
return nil, fmt.Errorf("DNS ZoneID not set")
}
pb.HostedZoneID = hostedZoneID
}
policy, err := pb.BuildAWSIAMPolicy()
if err != nil {
return nil, fmt.Errorf("error building IAM policy: %v", err)
}
json, err := policy.AsJSON()
if err != nil {
return nil, fmt.Errorf("error building IAM policy: %v", err)
}
return bytes.NewReader([]byte(json)), nil
}

View File

@ -399,11 +399,6 @@ func (c *ApplyClusterCmd) Run() error {
if err != nil {
return err
}
dnszone, err := findZone(cluster, cloud)
if err != nil {
return err
}
modelContext.HostedZoneID = dnszone.ID()
}
clusterTags, err := buildCloudupTags(cluster)

View File

@ -40,7 +40,7 @@ type IAMRolePolicy struct {
// The PolicyDocument to create as an inline policy.
// If the PolicyDocument is empty, the policy will be removed.
PolicyDocument *fi.ResourceHolder
PolicyDocument fi.Resource
}
func (e *IAMRolePolicy) Find(c *fi.Context) (*IAMRolePolicy, error) {
@ -97,7 +97,7 @@ func (s *IAMRolePolicy) CheckChanges(a, e, changes *IAMRolePolicy) error {
}
func (_ *IAMRolePolicy) ShouldCreate(a, e, changes *IAMRolePolicy) (bool, error) {
ePolicy, err := e.PolicyDocument.AsString()
ePolicy, err := e.policyDocumentString()
if err != nil {
return false, fmt.Errorf("error rendering PolicyDocument: %v", err)
}
@ -109,7 +109,7 @@ func (_ *IAMRolePolicy) ShouldCreate(a, e, changes *IAMRolePolicy) (bool, error)
}
func (_ *IAMRolePolicy) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *IAMRolePolicy) error {
policy, err := e.PolicyDocument.AsString()
policy, err := e.policyDocumentString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)
}
@ -143,12 +143,9 @@ func (_ *IAMRolePolicy) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *IAMRoleP
if changes.PolicyDocument != nil {
glog.V(2).Infof("Applying changed role policy to %q:", *e.Name)
actualPolicy := ""
if a.PolicyDocument != nil {
actualPolicy, err = a.PolicyDocument.AsString()
if err != nil {
return fmt.Errorf("error reading actual policy document: %v", err)
}
actualPolicy, err := a.policyDocumentString()
if err != nil {
return fmt.Errorf("error reading actual policy document: %v", err)
}
if actualPolicy == policy {
@ -180,6 +177,13 @@ func (_ *IAMRolePolicy) RenderAWS(t *awsup.AWSAPITarget, a, e, changes *IAMRoleP
return nil // No tags in IAM
}
func (e *IAMRolePolicy) policyDocumentString() (string, error) {
if e.PolicyDocument == nil {
return "", nil
}
return fi.ResourceAsString(e.PolicyDocument)
}
type terraformIAMRolePolicy struct {
Name *string `json:"name"`
Role *terraform.Literal `json:"role"`
@ -187,15 +191,14 @@ type terraformIAMRolePolicy struct {
}
func (_ *IAMRolePolicy) RenderTerraform(t *terraform.TerraformTarget, a, e, changes *IAMRolePolicy) error {
{
policyString, err := e.PolicyDocument.AsString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)
}
if policyString == "" {
// A deletion; we simply don't render; terraform will observe the removal
return nil
}
policyString, err := e.policyDocumentString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)
}
if policyString == "" {
// A deletion; we simply don't render; terraform will observe the removal
return nil
}
policy, err := t.AddFile("aws_iam_role_policy", *e.Name, "policy", e.PolicyDocument)
@ -223,15 +226,13 @@ type cloudformationIAMRolePolicy struct {
}
func (_ *IAMRolePolicy) RenderCloudformation(t *cloudformation.CloudformationTarget, a, e, changes *IAMRolePolicy) error {
{
policyString, err := e.PolicyDocument.AsString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)
}
if policyString == "" {
// A deletion; we simply don't render; cloudformation will observe the removal
return nil
}
policyString, err := e.policyDocumentString()
if err != nil {
return fmt.Errorf("error rendering PolicyDocument: %v", err)
}
if policyString == "" {
// A deletion; we simply don't render; cloudformation will observe the removal
return nil
}
tf := &cloudformationIAMRolePolicy{
@ -240,13 +241,8 @@ func (_ *IAMRolePolicy) RenderCloudformation(t *cloudformation.CloudformationTar
}
{
jsonString, err := e.PolicyDocument.AsBytes()
if err != nil {
return err
}
data := make(map[string]interface{})
err = json.Unmarshal(jsonString, &data)
err = json.Unmarshal([]byte(policyString), &data)
if err != nil {
return fmt.Errorf("error parsing PolicyDocument: %v", err)
}