Configuration of admin access to ports 22 and master-443

Fix #143
This commit is contained in:
Justin Santa Barbara 2016-07-14 10:07:50 -04:00
parent aa190b8c3f
commit 302f23463e
12 changed files with 79 additions and 10 deletions

View File

@ -36,6 +36,7 @@ type CreateClusterCmd struct {
VPCID string
NetworkCIDR string
DNSZone string
AdminAccess string
}
var createCluster CreateClusterCmd
@ -93,6 +94,7 @@ func init() {
cmd.Flags().StringVar(&createCluster.DNSZone, "dns-zone", "", "DNS hosted zone to use (defaults to last two components of cluster name)")
cmd.Flags().StringVar(&createCluster.OutDir, "out", "", "Path to write any local output")
cmd.Flags().StringVar(&createCluster.AdminAccess, "admin-access", "", "Restrict access to admin endpoints (SSH, HTTPS) to this CIDR. If not set, access will not be restricted by IP.")
}
var EtcdClusters = []string{"main", "events"}
@ -332,6 +334,10 @@ func (c *CreateClusterCmd) Run() error {
c.SSHPublicKey = utils.ExpandPath(c.SSHPublicKey)
}
if c.AdminAccess != "" {
cluster.Spec.AdminAccess = []string{c.AdminAccess}
}
err = cluster.PerformAssignments()
if err != nil {
return fmt.Errorf("error populating configuration: %v", err)

View File

@ -1,8 +1,24 @@
# Detailed description of arguments
## admin-access
`admin-access` controls the CIDR which can access the admin endpoints (SSH to each node, HTTPS to the master).
It maps to `Cluster.Spec.AdminAccess`
If not specified, no IP level restrictions will apply (though there are still restrictions, for example you need
a permitted SSH key to access the SSH service!).
Currently this can only be a single CIDR.
Examples:
`--admin-access=18.0.0.0/8` to restrict to IPs in the 18.0.0.0/8 CIDR
## dns-zone
dns-zone controls the Route53 hosted zone in which DNS records will be created. It can either by the name
`dns-zone` controls the Route53 hosted zone in which DNS records will be created. It can either by the name
of the hosted zone (`example.com`), or it can be the ID of the hosted zone (`Z1GABCD1ABC2DEF`)
Suppose you're creating a cluster named "dev.kubernetes.example.com`:
@ -17,3 +33,7 @@ If you don't specify a dns-zone, kops will list all your hosted zones, and choos
is a a suffix of your cluster name. So for `dev.kubernetes.example.com`, if you have `kubernetes.example.com`,
`example.com` and `somethingelse.example.com`, it would choose `kubernetes.example.com`. `example.com` matches
but is shorter; `somethingelse.example.com` is not a suffix-match.
Examples:
`--dns-zone=example.com` to use the hosted zone with a name of example.com

View File

@ -35,7 +35,7 @@ securityGroupRule/egress-api-lb:
# HTTPS to the master ELB is allowed (for API access)
securityGroupRule/https-external-to-api:
securityGroup: securityGroup/api.{{ ClusterName }}
cidr: 0.0.0.0/0
cidr: {{ AdminCIDR }}
protocol: tcp
fromPort: 443
toPort: 443

View File

@ -5,7 +5,7 @@
# HTTPS to the master is allowed (for API access)
securityGroupRule/https-external-to-master:
securityGroup: securityGroup/masters.{{ ClusterName }}
cidr: 0.0.0.0/0
cidr: {{ AdminCIDR }}
protocol: tcp
fromPort: 443
toPort: 443

View File

@ -24,10 +24,10 @@ securityGroupRule/master-egress:
egress: true
cidr: 0.0.0.0/0
# SSH is open to the world
# SSH is open to AdminCIDR
securityGroupRule/ssh-external-to-master:
securityGroup: securityGroup/masters.{{ ClusterName }}
cidr: 0.0.0.0/0
cidr: {{ AdminCIDR }}
protocol: tcp
fromPort: 22
toPort: 22

View File

@ -27,7 +27,7 @@ securityGroupRule/node-egress:
# SSH is open to the world
securityGroupRule/ssh-external-to-node:
securityGroup: securityGroup/nodes.{{ ClusterName }}
cidr: 0.0.0.0/0
cidr: {{ AdminCIDR }}
protocol: tcp
fromPort: 22
toPort: 22

View File

@ -6,9 +6,9 @@ persistentDisk/kubernetes-master-{{ ClusterName }}:
volumeType: {{ or .MasterVolumeType "pd-ssd" }}
# Open master HTTPS
firewallRule/kubernetes-master-https-{{ ClusterName }}:
firewallRule/kubernetes-master-https-{{ ClusterName }}-{{ AdminCIDR }}:
network: network/default
sourceRanges: 0.0.0.0/0
sourceRanges: {{ AdminCIDR }}
targetTags: {{ .MasterTag }}
allowed: tcp:443

View File

@ -13,8 +13,8 @@ firewallRule/{{ $networkName }}-default-internal:
- icmp
# SSH is open to the world
firewallRule/{{ $networkName }}-default-ssh:
firewallRule/{{ $networkName }}-default-ssh-{{ AdminCIDR }}:
network: network/default
sourceRanges: 0.0.0.0/0
sourceRanges: {{ AdminCIDR }}
allowed: tcp:22

View File

@ -90,6 +90,10 @@ type ClusterSpec struct {
// It cannot overlap ServiceClusterIPRange
NonMasqueradeCIDR string `json:"nonMasqueradeCIDR,omitempty"`
// AdminAccess determines the permitted access to the admin endpoints (SSH & master HTTPS)
// Currently only a single CIDR is supported (though a richer grammar could be added in future)
AdminAccess []string `json:"adminAccess,omitempty"`
//NetworkProvider string `json:",omitempty"`
//
//HairpinMode string `json:",omitempty"`
@ -273,6 +277,17 @@ func (c *Cluster) PerformAssignments() error {
return nil
}
// FillDefaults populates default values.
// This is different from PerformAssignments, because these values are changeable, and thus we don't need to
// store them (i.e. we don't need to 'lock them')
func (c *Cluster) FillDefaults() error {
if len(c.Spec.AdminAccess) == 0 {
c.Spec.AdminAccess = append(c.Spec.AdminAccess, "0.0.0.0/0")
}
return nil
}
func (z *ClusterZoneSpec) performAssignments(c *Cluster) error {
if z.CIDR == "" {
cidr, err := z.assignCIDR(c)

View File

@ -178,6 +178,17 @@ func (c *Cluster) Validate(strict bool) error {
}
}
if strict && len(c.Spec.AdminAccess) == 0 {
return fmt.Errorf("AdminAccess not configured")
}
for _, adminAccess := range c.Spec.AdminAccess {
_, _, err := net.ParseCIDR(adminAccess)
if err != nil {
return fmt.Errorf("AdminAccess rule %q could not be parsed (invalid CIDR)", adminAccess)
}
}
return nil
}

View File

@ -147,6 +147,11 @@ func (c *CreateClusterCmd) Run() error {
return err
}
err = cluster.FillDefaults()
if err != nil {
return err
}
// Check that instance groups are defined in valid zones
{
clusterZones := make(map[string]*api.ClusterZoneSpec)

View File

@ -51,6 +51,7 @@ func (tf *TemplateFunctions) AddTo(dest template.FuncMap) {
dest["EtcdClusterMemberTags"] = tf.EtcdClusterMemberTags
dest["SharedVPC"] = tf.SharedVPC
dest["WellKnownServiceIP"] = tf.WellKnownServiceIP
dest["AdminCIDR"] = tf.AdminCIDR
}
func (tf *TemplateFunctions) EtcdClusterMemberTags(etcd *api.EtcdClusterSpec, m *api.EtcdMemberSpec) map[string]string {
@ -77,3 +78,14 @@ func (tf *TemplateFunctions) EtcdClusterMemberTags(etcd *api.EtcdClusterSpec, m
func (tf *TemplateFunctions) SharedVPC() bool {
return tf.cluster.Spec.NetworkID != ""
}
// AdminCIDR returns the single CIDR that is allowed access to the admin ports of the cluster (22, 443 on master)
func (tf *TemplateFunctions) AdminCIDR() (string, error) {
if len(tf.cluster.Spec.AdminAccess) == 0 {
return "0.0.0.0/0", nil
}
if len(tf.cluster.Spec.AdminAccess) == 1 {
return tf.cluster.Spec.AdminAccess[0], nil
}
return "", fmt.Errorf("Multiple AdminAccess rules are not (currently) supported")
}