Merge pull request #11853 from johngmyers/override-issuer

Allow overriding the ServiceAccountIssuer for IRSA
This commit is contained in:
Kubernetes Prow Robot 2021-07-01 04:43:54 -07:00 committed by GitHub
commit 19ffc06d3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 70 additions and 101 deletions

View File

@ -410,11 +410,7 @@ func (b *IAMModelBuilder) buildAWSIAMRolePolicy(role iam.Subject) (fi.Resource,
var policy string var policy string
serviceAccount, ok := role.ServiceAccount() serviceAccount, ok := role.ServiceAccount()
if ok { if ok {
serviceAccountIssuer, err := iam.ServiceAccountIssuer(&b.Cluster.Spec) oidcProvider := strings.TrimPrefix(*b.Cluster.Spec.KubeAPIServer.ServiceAccountIssuer, "https://")
if err != nil {
return nil, err
}
oidcProvider := strings.TrimPrefix(serviceAccountIssuer, "https://")
iamPolicy := &iam.Policy{ iamPolicy := &iam.Policy{
Version: iam.PolicyDefaultVersion, Version: iam.PolicyDefaultVersion,

View File

@ -17,7 +17,6 @@ limitations under the License.
package awsmodel package awsmodel
import ( import (
"k8s.io/kops/pkg/model/iam"
"k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks" "k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
) )
@ -42,11 +41,6 @@ func (b *OIDCProviderBuilder) Build(c *fi.ModelBuilderContext) error {
return nil return nil
} }
serviceAccountIssuer, err := iam.ServiceAccountIssuer(&b.Cluster.Spec)
if err != nil {
return err
}
fingerprints := getFingerprints() fingerprints := getFingerprints()
thumbprints := []*string{} thumbprints := []*string{}
@ -58,7 +52,7 @@ func (b *OIDCProviderBuilder) Build(c *fi.ModelBuilderContext) error {
c.AddTask(&awstasks.IAMOIDCProvider{ c.AddTask(&awstasks.IAMOIDCProvider{
Name: fi.String(b.ClusterName()), Name: fi.String(b.ClusterName()),
Lifecycle: b.Lifecycle, Lifecycle: b.Lifecycle,
URL: fi.String(serviceAccountIssuer), URL: b.Cluster.Spec.KubeAPIServer.ServiceAccountIssuer,
ClientIDs: []*string{fi.String(defaultAudience)}, ClientIDs: []*string{fi.String(defaultAudience)},
Tags: b.CloudTags(b.ClusterName(), false), Tags: b.CloudTags(b.ClusterName(), false),
Thumbprints: thumbprints, Thumbprints: thumbprints,

View File

@ -32,8 +32,8 @@ go_library(
"//pkg/apis/kops:go_default_library", "//pkg/apis/kops:go_default_library",
"//pkg/apis/kops/util:go_default_library", "//pkg/apis/kops/util:go_default_library",
"//pkg/assets:go_default_library", "//pkg/assets:go_default_library",
"//pkg/dns:go_default_library",
"//pkg/k8sversion:go_default_library", "//pkg/k8sversion:go_default_library",
"//pkg/model/iam:go_default_library",
"//pkg/wellknownports:go_default_library", "//pkg/wellknownports:go_default_library",
"//upup/pkg/fi:go_default_library", "//upup/pkg/fi:go_default_library",
"//upup/pkg/fi/cloudup/gce:go_default_library", "//upup/pkg/fi/cloudup/gce:go_default_library",

View File

@ -17,10 +17,14 @@ limitations under the License.
package components package components
import ( import (
"fmt"
"strings"
"k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/model/iam" "k8s.io/kops/pkg/dns"
"k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/loader" "k8s.io/kops/upup/pkg/fi/loader"
"k8s.io/kops/util/pkg/vfs"
) )
// DiscoveryOptionsBuilder adds options for identity discovery to the model (mostly kube-apiserver) // DiscoveryOptionsBuilder adds options for identity discovery to the model (mostly kube-apiserver)
@ -33,6 +37,10 @@ var _ loader.OptionsBuilder = &DiscoveryOptionsBuilder{}
func (b *DiscoveryOptionsBuilder) BuildOptions(o interface{}) error { func (b *DiscoveryOptionsBuilder) BuildOptions(o interface{}) error {
clusterSpec := o.(*kops.ClusterSpec) clusterSpec := o.(*kops.ClusterSpec)
if clusterSpec.KubeAPIServer == nil {
clusterSpec.KubeAPIServer = &kops.KubeAPIServerConfig{}
}
if b.IsKubernetesLT("1.20") { if b.IsKubernetesLT("1.20") {
if clusterSpec.KubeAPIServer.FeatureGates == nil { if clusterSpec.KubeAPIServer.FeatureGates == nil {
return nil return nil
@ -42,31 +50,61 @@ func (b *DiscoveryOptionsBuilder) BuildOptions(o interface{}) error {
} }
} }
if clusterSpec.KubeAPIServer == nil {
clusterSpec.KubeAPIServer = &kops.KubeAPIServerConfig{}
}
kubeAPIServer := clusterSpec.KubeAPIServer kubeAPIServer := clusterSpec.KubeAPIServer
if len(kubeAPIServer.APIAudiences) == 0 { if len(kubeAPIServer.APIAudiences) == 0 {
kubeAPIServer.APIAudiences = []string{"kubernetes.svc.default"} kubeAPIServer.APIAudiences = []string{"kubernetes.svc.default"}
} }
serviceAccountIssuer, err := iam.ServiceAccountIssuer(clusterSpec) if kubeAPIServer.ServiceAccountIssuer == nil {
said := clusterSpec.ServiceAccountIssuerDiscovery
var serviceAccountIssuer string
if said != nil && said.DiscoveryStore != "" {
store := said.DiscoveryStore
base, err := vfs.Context.BuildVfsPath(store)
if err != nil {
return fmt.Errorf("error parsing locationStore=%q: %w", store, err)
}
switch base := base.(type) {
case *vfs.S3Path:
serviceAccountIssuer, err = base.GetHTTPsUrl()
if err != nil { if err != nil {
return err return err
} }
case *vfs.MemFSPath:
if !base.IsClusterReadable() {
// If this _is_ a test, we should call MarkClusterReadable
return fmt.Errorf("locationStore=%q is only supported in tests", store)
}
serviceAccountIssuer = strings.Replace(base.Path(), "memfs://", "https://", 1)
default:
return fmt.Errorf("locationStore=%q is of unexpected type %T", store, base)
}
} else {
if dns.IsGossipHostname(clusterSpec.MasterInternalName) {
serviceAccountIssuer = "https://kubernetes.default"
} else if supportsPublicJWKS(clusterSpec) {
serviceAccountIssuer = "https://" + clusterSpec.MasterPublicName
} else {
serviceAccountIssuer = "https://" + clusterSpec.MasterInternalName
}
}
kubeAPIServer.ServiceAccountIssuer = &serviceAccountIssuer kubeAPIServer.ServiceAccountIssuer = &serviceAccountIssuer
}
kubeAPIServer.ServiceAccountJWKSURI = fi.String(*kubeAPIServer.ServiceAccountIssuer + "/openid/v1/jwks")
// We set apiserver ServiceAccountKey and ServiceAccountSigningKeyFile in nodeup // We set apiserver ServiceAccountKey and ServiceAccountSigningKeyFile in nodeup
if kubeAPIServer.ServiceAccountJWKSURI == nil {
jwksURI, err := iam.ServiceAccountIssuer(clusterSpec)
if err != nil {
return err
}
kubeAPIServer.ServiceAccountJWKSURI = fi.String(jwksURI + "/openid/v1/jwks")
}
return nil return nil
} }
func supportsPublicJWKS(clusterSpec *kops.ClusterSpec) bool {
if !fi.BoolValue(clusterSpec.KubeAPIServer.AnonymousAuth) {
return false
}
for _, cidr := range clusterSpec.KubernetesAPIAccess {
if cidr == "0.0.0.0/0" || cidr == "::/0" {
return true
}
}
return false
}

View File

@ -12,7 +12,6 @@ go_library(
deps = [ deps = [
"//pkg/apis/kops:go_default_library", "//pkg/apis/kops:go_default_library",
"//pkg/apis/kops/model:go_default_library", "//pkg/apis/kops/model:go_default_library",
"//pkg/dns:go_default_library",
"//pkg/util/stringorslice:go_default_library", "//pkg/util/stringorslice:go_default_library",
"//pkg/wellknownusers:go_default_library", "//pkg/wellknownusers:go_default_library",
"//upup/pkg/fi:go_default_library", "//upup/pkg/fi:go_default_library",

View File

@ -18,15 +18,11 @@ package iam
import ( import (
"fmt" "fmt"
"strings"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/types"
"k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/dns"
"k8s.io/kops/pkg/wellknownusers" "k8s.io/kops/pkg/wellknownusers"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/util/pkg/vfs"
) )
// Subject represents an IAM identity, to which permissions are granted. // Subject represents an IAM identity, to which permissions are granted.
@ -111,50 +107,6 @@ func BuildNodeRoleSubject(igRole kops.InstanceGroupRole, enableLifecycleHookPerm
} }
} }
// ServiceAccountIssuer determines the issuer in the ServiceAccount JWTs
func ServiceAccountIssuer(clusterSpec *kops.ClusterSpec) (string, error) {
said := clusterSpec.ServiceAccountIssuerDiscovery
if said != nil && said.DiscoveryStore != "" {
store := said.DiscoveryStore
base, err := vfs.Context.BuildVfsPath(store)
if err != nil {
return "", fmt.Errorf("error parsing locationStore=%q: %w", store, err)
}
switch base := base.(type) {
case *vfs.S3Path:
return base.GetHTTPsUrl()
case *vfs.MemFSPath:
if !base.IsClusterReadable() {
// If this _is_ a test, we should call MarkClusterReadable
return "", fmt.Errorf("locationStore=%q is only supported in tests", store)
}
return strings.Replace(base.Path(), "memfs://", "https://", 1), nil
default:
return "", fmt.Errorf("locationStore=%q is of unexpected type %T", store, base)
}
} else {
if dns.IsGossipHostname(clusterSpec.MasterInternalName) {
return "https://kubernetes.default", nil
}
if supportsPublicJWKS(clusterSpec) {
return "https://" + clusterSpec.MasterPublicName, nil
}
return "https://" + clusterSpec.MasterInternalName, nil
}
}
func supportsPublicJWKS(clusterSpec *kops.ClusterSpec) bool {
if !fi.BoolValue(clusterSpec.KubeAPIServer.AnonymousAuth) {
return false
}
for _, cidr := range clusterSpec.KubernetesAPIAccess {
if cidr == "0.0.0.0/0" || cidr == "::/0" {
return true
}
}
return false
}
// AddServiceAccountRole adds the appropriate mounts / env vars to enable a pod to use a service-account role // AddServiceAccountRole adds the appropriate mounts / env vars to enable a pod to use a service-account role
func AddServiceAccountRole(context *IAMModelContext, podSpec *corev1.PodSpec, serviceAccountRole Subject) error { func AddServiceAccountRole(context *IAMModelContext, podSpec *corev1.PodSpec, serviceAccountRole Subject) error {
cloudProvider := kops.CloudProviderID(context.Cluster.Spec.CloudProvider) cloudProvider := kops.CloudProviderID(context.Cluster.Spec.CloudProvider)

View File

@ -28,7 +28,6 @@ import (
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"k8s.io/kops/pkg/apis/kops" "k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/model/iam"
"k8s.io/kops/upup/pkg/fi" "k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/fitasks" "k8s.io/kops/upup/pkg/fi/fitasks"
) )
@ -68,12 +67,7 @@ func (b *IssuerDiscoveryModelBuilder) Build(c *fi.ModelBuilderContext) error {
SigningKey: skTask, SigningKey: skTask,
} }
serviceAccountIssuer, err := iam.ServiceAccountIssuer(&b.Cluster.Spec) discovery, err := buildDiscoveryJSON(*b.Cluster.Spec.KubeAPIServer.ServiceAccountIssuer)
if err != nil {
return err
}
discovery, err := buildDiscoveryJSON(serviceAccountIssuer)
if err != nil { if err != nil {
return err return err
} }

View File

@ -950,7 +950,7 @@ func (b *BootstrapChannelBuilder) buildAddons(c *fi.ModelBuilderContext) (*chann
}) })
} }
if kops.CloudProviderID(b.Cluster.Spec.CloudProvider) == kops.CloudProviderAWS { if kops.CloudProviderID(b.Cluster.Spec.CloudProvider) == kops.CloudProviderAWS && b.Cluster.Spec.KubeAPIServer.ServiceAccountIssuer != nil {
awsModelContext := &awsmodel.AWSModelContext{ awsModelContext := &awsmodel.AWSModelContext{
KopsModelContext: b.KopsModelContext, KopsModelContext: b.KopsModelContext,
} }

View File

@ -21,7 +21,7 @@ spec:
name: master-us-test-1a name: master-us-test-1a
name: events name: events
iam: {} iam: {}
kubernetesVersion: v1.14.6 kubernetesVersion: v1.20.6
masterInternalName: api.internal.minimal.example.com masterInternalName: api.internal.minimal.example.com
masterPublicName: api.minimal.example.com masterPublicName: api.minimal.example.com
additionalSans: additionalSans:

View File

@ -1,7 +1,7 @@
apiVersion: v1 apiVersion: v1
data: data:
config.yaml: | config.yaml: |
{"cloud":"aws","configBase":"memfs://clusters.example.com/minimal.example.com"} {"cloud":"aws","configBase":"memfs://clusters.example.com/minimal.example.com","server":{"Listen":":3988","provider":{"aws":{"nodesRoles":["kops-custom-node-role","nodes.minimal.example.com"],"Region":"us-east-1"}},"serverKeyPath":"/etc/kubernetes/kops-controller/pki/kops-controller.key","serverCertificatePath":"/etc/kubernetes/kops-controller/pki/kops-controller.crt","caBasePath":"/etc/kubernetes/kops-controller/pki","signingCAs":["ca"],"certNames":["kubelet","kubelet-server","kube-proxy"]}}
kind: ConfigMap kind: ConfigMap
metadata: metadata:
creationTimestamp: null creationTimestamp: null
@ -32,6 +32,8 @@ spec:
k8s-app: kops-controller k8s-app: kops-controller
template: template:
metadata: metadata:
annotations:
dns.alpha.kubernetes.io/internal: kops-controller.internal.minimal.example.com
labels: labels:
k8s-addon: kops-controller.addons.k8s.io k8s-addon: kops-controller.addons.k8s.io
k8s-app: kops-controller k8s-app: kops-controller

View File

@ -6,7 +6,7 @@ spec:
addons: addons:
- id: k8s-1.16 - id: k8s-1.16
manifest: kops-controller.addons.k8s.io/k8s-1.16.yaml manifest: kops-controller.addons.k8s.io/k8s-1.16.yaml
manifestHash: 40ffaead818a21b374588ed2f94b517b9569c71f manifestHash: 90b1206425c7a034147e2373ea00193018a1fb4a
name: kops-controller.addons.k8s.io name: kops-controller.addons.k8s.io
needsRollingUpdate: control-plane needsRollingUpdate: control-plane
selector: selector:
@ -17,17 +17,11 @@ spec:
selector: selector:
k8s-addon: core.addons.k8s.io k8s-addon: core.addons.k8s.io
- id: k8s-1.12 - id: k8s-1.12
manifest: kube-dns.addons.k8s.io/k8s-1.12.yaml manifest: coredns.addons.k8s.io/k8s-1.12.yaml
manifestHash: 470684b80af41aa53c25bc5ac1a78b259c93336b manifestHash: 004bda4e250d9cec5d5f3e732056020b78b0ab88
name: kube-dns.addons.k8s.io name: coredns.addons.k8s.io
selector: selector:
k8s-addon: kube-dns.addons.k8s.io k8s-addon: coredns.addons.k8s.io
- id: k8s-1.8
manifest: rbac.addons.k8s.io/k8s-1.8.yaml
manifestHash: 38ba4e0078bb1225421114f76f121c2277ca92ac
name: rbac.addons.k8s.io
selector:
k8s-addon: rbac.addons.k8s.io
- id: k8s-1.9 - id: k8s-1.9
manifest: kubelet-api.rbac.addons.k8s.io/k8s-1.9.yaml manifest: kubelet-api.rbac.addons.k8s.io/k8s-1.9.yaml
manifestHash: 8ee090e41be5e8bcd29ee799b1608edcd2dd8b65 manifestHash: 8ee090e41be5e8bcd29ee799b1608edcd2dd8b65