-
Notifications
You must be signed in to change notification settings - Fork 169
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ARO-4373 generate keypair and oidc docs for miwi clusters
- Loading branch information
1 parent
e9cdd84
commit dafefa0
Showing
2 changed files
with
208 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
package oidcbuilder | ||
|
||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the Apache License 2.0. | ||
|
||
import ( | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"encoding/base64" | ||
"encoding/json" | ||
"encoding/pem" | ||
"fmt" | ||
|
||
"github.com/pkg/errors" | ||
"gopkg.in/square/go-jose.v2" | ||
) | ||
|
||
func CreateKeyPair() (encPrivateKey []byte, encPublicKey []byte, err error) { | ||
bitSize := 4096 | ||
|
||
// Generate RSA keypair | ||
privateKey, err := rsa.GenerateKey(rand.Reader, bitSize) | ||
if err != nil { | ||
return nil, nil, errors.Wrapf(err, "failed to generate private key") | ||
} | ||
encodedPrivateKey := pem.EncodeToMemory(&pem.Block{ | ||
Type: "RSA PRIVATE KEY", | ||
Headers: nil, | ||
Bytes: x509.MarshalPKCS1PrivateKey(privateKey), | ||
}) | ||
|
||
// Generate public key from private keypair | ||
pubKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) | ||
if err != nil { | ||
return nil, nil, errors.Wrapf(err, "failed to generate public key from private") | ||
} | ||
encodedPublicKey := pem.EncodeToMemory(&pem.Block{ | ||
Type: "PUBLIC KEY", | ||
Headers: nil, | ||
Bytes: pubKeyBytes, | ||
}) | ||
|
||
return encodedPrivateKey, encodedPublicKey, nil | ||
} | ||
|
||
type JSONWebKeySet struct { | ||
Keys []jose.JSONWebKey `json:"keys"` | ||
} | ||
|
||
// buildJSONWebKeySet builds JSON web key set from the public key | ||
func BuildJSONWebKeySet(publicKeyContent []byte) ([]byte, error) { | ||
block, _ := pem.Decode(publicKeyContent) | ||
if block == nil { | ||
return nil, errors.Errorf("Failed to decode PEM file") | ||
} | ||
|
||
publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "Failed to parse key content") | ||
} | ||
|
||
var alg jose.SignatureAlgorithm | ||
switch publicKey.(type) { | ||
case *rsa.PublicKey: | ||
alg = jose.RS256 | ||
default: | ||
return nil, errors.Errorf("Public key is not of type RSA") | ||
} | ||
|
||
kid, err := keyIDFromPublicKey(publicKey) | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "Failed to fetch key ID from public key") | ||
} | ||
|
||
var keys []jose.JSONWebKey | ||
keys = append(keys, jose.JSONWebKey{ | ||
Key: publicKey, | ||
KeyID: kid, | ||
Algorithm: string(alg), | ||
Use: "sig", | ||
}) | ||
|
||
keySet, err := json.MarshalIndent(JSONWebKeySet{Keys: keys}, "", " ") | ||
if err != nil { | ||
return nil, errors.Wrapf(err, "JSON encoding of web key set failed") | ||
} | ||
|
||
return keySet, nil | ||
} | ||
|
||
// keyIDFromPublicKey derives a key ID non-reversibly from a public key | ||
// reference: https://github.com/kubernetes/kubernetes/blob/v1.21.0/pkg/serviceaccount/jwt.go#L89-L111 | ||
func keyIDFromPublicKey(publicKey interface{}) (string, error) { | ||
publicKeyDERBytes, err := x509.MarshalPKIXPublicKey(publicKey) | ||
if err != nil { | ||
return "", errors.Wrapf(err, "Failed to serialize public key to DER format") | ||
} | ||
|
||
hasher := crypto.SHA256.New() | ||
hasher.Write(publicKeyDERBytes) | ||
publicKeyDERHash := hasher.Sum(nil) | ||
|
||
keyID := base64.RawURLEncoding.EncodeToString(publicKeyDERHash) | ||
|
||
return keyID, nil | ||
} | ||
|
||
const ( | ||
discoveryDocumentTemplate = `{ | ||
"issuer": "%s", | ||
"jwks_uri": "%s/openid/v1/jwks", | ||
"response_types_supported": [ | ||
"id_token" | ||
], | ||
"subject_types_supported": [ | ||
"public" | ||
], | ||
"id_token_signing_alg_values_supported": [ | ||
"RS256" | ||
], | ||
"claims_supported": [ | ||
"aud", | ||
"exp", | ||
"sub", | ||
"iat", | ||
"iss", | ||
"sub" | ||
] | ||
}` | ||
) | ||
|
||
func GenerateDiscoveryDocument(bucketURL string) string { | ||
return fmt.Sprintf(discoveryDocumentTemplate, bucketURL, bucketURL) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package oidcbuilder | ||
|
||
// Copyright (c) Microsoft Corporation. | ||
// Licensed under the Apache License 2.0. | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"strings" | ||
|
||
azstorage "github.com/Azure/azure-sdk-for-go/storage" | ||
) | ||
|
||
type OIDCBuilder struct { | ||
privateKey []byte | ||
publicKey []byte | ||
endpointUrl string | ||
} | ||
|
||
func NewOIDCBuilder() *OIDCBuilder { | ||
return &OIDCBuilder{} | ||
} | ||
|
||
func (b *OIDCBuilder) EnsureOIDCDocs(ctx context.Context, oidcContainerName string, blobService *azstorage.BlobStorageClient) error { | ||
privateKey, publicKey, err := CreateKeyPair() | ||
b.privateKey = privateKey | ||
b.publicKey = publicKey | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// Create the OIDC configuration | ||
discoveryDocument := GenerateDiscoveryDocument(b.endpointUrl) | ||
|
||
// Create the OIDC key list | ||
jwks, err := BuildJSONWebKeySet(b.publicKey) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return populateOidcFolder(oidcContainerName, discoveryDocument, jwks, blobService) | ||
} | ||
|
||
func (b *OIDCBuilder) SetOIDCEndpointUrl(storageEndpoint string, containerName string) { | ||
b.endpointUrl = fmt.Sprintf("https://%s/%s", storageEndpoint, containerName) | ||
} | ||
|
||
func (b *OIDCBuilder) GetEndpointUrl() string { | ||
return b.endpointUrl | ||
} | ||
|
||
func (b *OIDCBuilder) GetPrivateKey() string { | ||
return b.endpointUrl | ||
} | ||
|
||
func populateOidcFolder(containerName, body string, jwks []byte, blobService *azstorage.BlobStorageClient) error { | ||
bodyKey := ".well-known/openid-configuration" | ||
jwksKey := "openid/v1/jwks" | ||
|
||
err := blobService.GetContainerReference(containerName).GetBlobReference(bodyKey).CreateBlockBlobFromReader(strings.NewReader(body), nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
err = blobService.GetContainerReference(containerName).GetBlobReference(jwksKey).CreateBlockBlobFromReader(bytes.NewReader(jwks), nil) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} |