Merge pull request #11359 from olemarkus/oidc-split-builder

Split oidc_provider
This commit is contained in:
Kubernetes Prow Robot 2021-04-30 09:55:58 -07:00 committed by GitHub
commit e9747269de
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 178 additions and 128 deletions

2
pkg/model/BUILD.bazel generated
View File

@ -5,6 +5,7 @@ go_library(
srcs = [
"bootstrapscript.go",
"context.go",
"issuerdiscovery.go",
"manifests.go",
"master_volumes.go",
"names.go",
@ -47,6 +48,7 @@ go_library(
"//util/pkg/vfs:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library",
"//vendor/github.com/blang/semver/v4:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",

View File

@ -31,11 +31,9 @@ go_library(
"//upup/pkg/fi/cloudup/awstasks:go_default_library",
"//upup/pkg/fi/cloudup/awsup:go_default_library",
"//upup/pkg/fi/cloudup/spotinsttasks:go_default_library",
"//upup/pkg/fi/fitasks:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/aws/endpoints:go_default_library",
"//vendor/github.com/aws/aws-sdk-go/service/ec2:go_default_library",
"//vendor/gopkg.in/square/go-jose.v2:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
"//vendor/k8s.io/klog/v2:go_default_library",

View File

@ -17,21 +17,10 @@ limitations under the License.
package awsmodel
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"gopkg.in/square/go-jose.v2"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/pkg/model/iam"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/cloudup/awstasks"
"k8s.io/kops/upup/pkg/fi/fitasks"
)
// OIDCProviderBuilder configures IAM OIDC Provider
@ -41,16 +30,6 @@ type OIDCProviderBuilder struct {
Lifecycle *fi.Lifecycle
}
type oidcDiscovery struct {
Issuer string `json:"issuer"`
JWKSURI string `json:"jwks_uri"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
ResponseTypes []string `json:"response_types_supported"`
SubjectTypes []string `json:"subject_types_supported"`
SigningAlgs []string `json:"id_token_signing_alg_values_supported"`
ClaimsSupported []string `json:"claims_supported"`
}
var _ fi.ModelBuilder = &OIDCProviderBuilder{}
const (
@ -68,11 +47,6 @@ func (b *OIDCProviderBuilder) Build(c *fi.ModelBuilderContext) error {
return err
}
signingKeyTaskObject, found := c.Tasks["Keypair/service-account"]
if !found {
return fmt.Errorf("keypair/service-account task not found")
}
fingerprints := getFingerprints()
thumbprints := []*string{}
@ -81,36 +55,6 @@ func (b *OIDCProviderBuilder) Build(c *fi.ModelBuilderContext) error {
thumbprints = append(thumbprints, fi.String(fingerprint))
}
skTask := signingKeyTaskObject.(*fitasks.Keypair)
keys := &OIDCKeys{
SigningKey: skTask,
}
discovery, err := buildDiscoveryJSON(serviceAccountIssuer)
if err != nil {
return err
}
keysFile := &fitasks.ManagedFile{
Contents: keys,
Lifecycle: b.Lifecycle,
Location: fi.String("oidc/keys.json"),
Name: fi.String("keys.json"),
Base: fi.String(b.Cluster.Spec.PublicDataStore),
Public: fi.Bool(true),
}
c.AddTask(keysFile)
discoveryFile := &fitasks.ManagedFile{
Contents: fi.NewBytesResource(discovery),
Lifecycle: b.Lifecycle,
Location: fi.String("oidc/.well-known/openid-configuration"),
Name: fi.String("discovery.json"),
Base: fi.String(b.Cluster.Spec.PublicDataStore),
Public: fi.Bool(true),
}
c.AddTask(discoveryFile)
c.AddTask(&awstasks.IAMOIDCProvider{
Name: fi.String(b.ClusterName()),
Lifecycle: b.Lifecycle,
@ -123,76 +67,6 @@ func (b *OIDCProviderBuilder) Build(c *fi.ModelBuilderContext) error {
return nil
}
func buildDiscoveryJSON(issuerURL string) ([]byte, error) {
d := oidcDiscovery{
Issuer: fmt.Sprintf("%v/", issuerURL),
JWKSURI: fmt.Sprintf("%v/keys.json", issuerURL),
AuthorizationEndpoint: "urn:kubernetes:programmatic_authorization",
ResponseTypes: []string{"id_token"},
SubjectTypes: []string{"public"},
SigningAlgs: []string{"RS256"},
ClaimsSupported: []string{"sub", "iss"},
}
return json.MarshalIndent(d, "", "")
}
type KeyResponse struct {
Keys []jose.JSONWebKey `json:"keys"`
}
type OIDCKeys struct {
SigningKey *fitasks.Keypair
}
// GetDependencies adds CA to the list of dependencies
func (o *OIDCKeys) GetDependencies(tasks map[string]fi.Task) []fi.Task {
return []fi.Task{
o.SigningKey,
}
}
func (o *OIDCKeys) Open() (io.Reader, error) {
certBytes, err := fi.ResourceAsBytes(o.SigningKey.Certificate())
if err != nil {
return nil, fmt.Errorf("failed to get cert: %w", err)
}
block, _ := pem.Decode(certBytes)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse cert: %w", err)
}
publicKey := cert.PublicKey
publicKeyDERBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("failed to serialize public key to DER format: %v", err)
}
hasher := crypto.SHA256.New()
hasher.Write(publicKeyDERBytes)
publicKeyDERHash := hasher.Sum(nil)
keyID := base64.RawURLEncoding.EncodeToString(publicKeyDERHash)
keys := []jose.JSONWebKey{
{
Key: publicKey,
KeyID: keyID,
Algorithm: string(jose.RS256),
Use: "sig",
},
}
keyResponse := KeyResponse{Keys: keys}
jsonBytes, err := json.MarshalIndent(keyResponse, "", "")
if err != nil {
return nil, fmt.Errorf("failed to marshal json: %w", err)
}
return bytes.NewReader(jsonBytes), nil
}
func getFingerprints() []string {
//These strings are the sha1 of the two possible S3 root CAs.

View File

@ -0,0 +1,171 @@
/*
Copyright 2019 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 model
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"gopkg.in/square/go-jose.v2"
"k8s.io/kops/pkg/apis/kops"
"k8s.io/kops/pkg/featureflag"
"k8s.io/kops/pkg/model/iam"
"k8s.io/kops/upup/pkg/fi"
"k8s.io/kops/upup/pkg/fi/fitasks"
)
// IssuerDiscoveryModelBuilder publish OIDC issuer discovery metadata
type IssuerDiscoveryModelBuilder struct {
*KopsModelContext
Lifecycle *fi.Lifecycle
Cluster *kops.Cluster
}
type oidcDiscovery struct {
Issuer string `json:"issuer"`
JWKSURI string `json:"jwks_uri"`
AuthorizationEndpoint string `json:"authorization_endpoint"`
ResponseTypes []string `json:"response_types_supported"`
SubjectTypes []string `json:"subject_types_supported"`
SigningAlgs []string `json:"id_token_signing_alg_values_supported"`
ClaimsSupported []string `json:"claims_supported"`
}
func (b *IssuerDiscoveryModelBuilder) Build(c *fi.ModelBuilderContext) error {
if !featureflag.PublicJWKS.Enabled() {
return nil
}
signingKeyTaskObject, found := c.Tasks["Keypair/service-account"]
if !found {
return fmt.Errorf("keypair/service-account task not found")
}
skTask := signingKeyTaskObject.(*fitasks.Keypair)
keys := &OIDCKeys{
SigningKey: skTask,
}
serviceAccountIssuer, err := iam.ServiceAccountIssuer(&b.Cluster.Spec)
if err != nil {
return err
}
discovery, err := buildDiscoveryJSON(serviceAccountIssuer)
if err != nil {
return err
}
keysFile := &fitasks.ManagedFile{
Contents: keys,
Lifecycle: b.Lifecycle,
Location: fi.String("/openid/v1/jwks"),
Name: fi.String("keys.json"),
Base: fi.String(b.Cluster.Spec.PublicDataStore),
Public: fi.Bool(true),
}
c.AddTask(keysFile)
discoveryFile := &fitasks.ManagedFile{
Contents: fi.NewBytesResource(discovery),
Lifecycle: b.Lifecycle,
Location: fi.String("oidc/.well-known/openid-configuration"),
Name: fi.String("discovery.json"),
Base: fi.String(b.Cluster.Spec.PublicDataStore),
Public: fi.Bool(true),
}
c.AddTask(discoveryFile)
return nil
}
func buildDiscoveryJSON(issuerURL string) ([]byte, error) {
d := oidcDiscovery{
Issuer: fmt.Sprintf("%v/", issuerURL),
JWKSURI: fmt.Sprintf("%v/openid/v1/jwks", issuerURL),
AuthorizationEndpoint: "urn:kubernetes:programmatic_authorization",
ResponseTypes: []string{"id_token"},
SubjectTypes: []string{"public"},
SigningAlgs: []string{"RS256"},
ClaimsSupported: []string{"sub", "iss"},
}
return json.MarshalIndent(d, "", "")
}
type KeyResponse struct {
Keys []jose.JSONWebKey `json:"keys"`
}
type OIDCKeys struct {
SigningKey *fitasks.Keypair
}
// GetDependencies adds CA to the list of dependencies
func (o *OIDCKeys) GetDependencies(tasks map[string]fi.Task) []fi.Task {
return []fi.Task{
o.SigningKey,
}
}
func (o *OIDCKeys) Open() (io.Reader, error) {
certBytes, err := fi.ResourceAsBytes(o.SigningKey.Certificate())
if err != nil {
return nil, fmt.Errorf("failed to get cert: %w", err)
}
block, _ := pem.Decode(certBytes)
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse cert: %w", err)
}
publicKey := cert.PublicKey
publicKeyDERBytes, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
return nil, fmt.Errorf("failed to serialize public key to DER format: %v", err)
}
hasher := crypto.SHA256.New()
hasher.Write(publicKeyDERBytes)
publicKeyDERHash := hasher.Sum(nil)
keyID := base64.RawURLEncoding.EncodeToString(publicKeyDERHash)
keys := []jose.JSONWebKey{
{
Key: publicKey,
KeyID: keyID,
Algorithm: string(jose.RS256),
Use: "sig",
},
}
keyResponse := KeyResponse{Keys: keys}
jsonBytes, err := json.MarshalIndent(keyResponse, "", "")
if err != nil {
return nil, fmt.Errorf("failed to marshal json: %w", err)
}
return bytes.NewReader(jsonBytes), nil
}

View File

@ -527,6 +527,11 @@ func (c *ApplyClusterCmd) Run(ctx context.Context) error {
KopsModelContext: modelContext,
Lifecycle: &clusterLifecycle,
},
&model.IssuerDiscoveryModelBuilder{
KopsModelContext: modelContext,
Lifecycle: &clusterLifecycle,
Cluster: cluster,
},
&kubeapiserver.KubeApiserverBuilder{
AssetBuilder: assetBuilder,
KopsModelContext: modelContext,