mirror of
https://github.com/storytold/irsa-manager.git
synced 2026-10-09 00:09:43 +00:00
keys, oidcIdP
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
module github.com/kkb0318/irsa-manager
|
module github.com/kkb0318/irsa-manager
|
||||||
|
|
||||||
go 1.22
|
go 1.21
|
||||||
|
|
||||||
toolchain go1.22.1
|
toolchain go1.21.8
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-jose/go-jose/v4 v4.0.1
|
github.com/go-jose/go-jose/v4 v4.0.1
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package selfhosted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/base64"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
jose "github.com/go-jose/go-jose/v4"
|
||||||
|
"k8s.io/client-go/util/keyutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// keyIDFromPublicKey derives a key ID non-reversibly from a public key.
|
||||||
|
//
|
||||||
|
// The Key ID is field on a given on JWTs and JWKs that help relying parties
|
||||||
|
// pick the correct key for verification when the identity party advertises
|
||||||
|
// multiple keys.
|
||||||
|
//
|
||||||
|
// Making the derivation non-reversible makes it impossible for someone to
|
||||||
|
// accidentally obtain the real key from the key ID and use it for token
|
||||||
|
// validation.
|
||||||
|
// This method is copied from
|
||||||
|
// https://github.com/kubernetes/kubernetes/blob/v1.29.3/pkg/serviceaccount/jwt.go#L99
|
||||||
|
func keyIDFromPublicKey(publicKey interface{}) (string, error) {
|
||||||
|
publicKeyDERBytes, err := x509.MarshalPKIXPublicKey(publicKey)
|
||||||
|
if err != nil {
|
||||||
|
return "", 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)
|
||||||
|
|
||||||
|
return keyID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type JWK struct {
|
||||||
|
Keys []jose.JSONWebKey `json:"keys"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewJWK(pub []byte) (*JWK, error) {
|
||||||
|
pubKeys, err := keyutil.ParsePublicKeysPEM(pub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pubKey := pubKeys[0]
|
||||||
|
var alg jose.SignatureAlgorithm
|
||||||
|
switch pubKey.(type) {
|
||||||
|
case *rsa.PublicKey:
|
||||||
|
alg = jose.RS256
|
||||||
|
default:
|
||||||
|
return nil, errors.New("public key is not RSA")
|
||||||
|
}
|
||||||
|
|
||||||
|
kid, err := keyIDFromPublicKey(pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var keys []jose.JSONWebKey
|
||||||
|
keys = append(keys, jose.JSONWebKey{
|
||||||
|
Key: pubKey,
|
||||||
|
KeyID: kid,
|
||||||
|
Algorithm: string(alg),
|
||||||
|
Use: "sig",
|
||||||
|
})
|
||||||
|
keys = append(keys, jose.JSONWebKey{
|
||||||
|
Key: pubKey,
|
||||||
|
KeyID: "",
|
||||||
|
Algorithm: string(alg),
|
||||||
|
Use: "sig",
|
||||||
|
})
|
||||||
|
return &JWK{Keys: keys}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *JWK) GetKeys() []jose.JSONWebKey {
|
||||||
|
return j.Keys
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package selfhosted
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
const rsaKeyID = "JHJehTTTZlsspKHT-GaJxK7Kd1NQgZJu3fyK6K_QDYU"
|
||||||
|
|
||||||
|
func TestJWK(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
filename string
|
||||||
|
expected string
|
||||||
|
expectErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "rsa",
|
||||||
|
filename: "testdata/rsa.pub",
|
||||||
|
expected: rsaKeyID,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "rsa",
|
||||||
|
filename: "testdata/ecdsa.pub",
|
||||||
|
expectErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
content, err := os.ReadFile(tt.filename)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
actual, err := NewJWK(content)
|
||||||
|
if tt.expectErr {
|
||||||
|
assert.Error(t, err, "")
|
||||||
|
} else {
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, tt.expected, actual.Keys[0].KeyID)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+34
-54
@@ -1,72 +1,52 @@
|
|||||||
package selfhosted
|
package selfhosted
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/pem"
|
||||||
"errors"
|
"os"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
|
||||||
"k8s.io/client-go/util/keyutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// keyIDFromPublicKey derives a key ID non-reversibly from a public key.
|
func createKeyPair() error {
|
||||||
//
|
// RSAキーペアの生成
|
||||||
// The Key ID is field on a given on JWTs and JWKs that help relying parties
|
privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
// pick the correct key for verification when the identity party advertises
|
|
||||||
// multiple keys.
|
|
||||||
//
|
|
||||||
// Making the derivation non-reversible makes it impossible for someone to
|
|
||||||
// accidentally obtain the real key from the key ID and use it for token
|
|
||||||
// validation.
|
|
||||||
// This method is copied from
|
|
||||||
// https://github.com/kubernetes/kubernetes/blob/v1.29.3/pkg/serviceaccount/jwt.go#L99
|
|
||||||
func keyIDFromPublicKey(publicKey interface{}) (string, error) {
|
|
||||||
publicKeyDERBytes, err := x509.MarshalPKIXPublicKey(publicKey)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("failed to serialize public key to DER format: %v", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
hasher := crypto.SHA256.New()
|
// private keyをPEM形式で保存
|
||||||
hasher.Write(publicKeyDERBytes)
|
privPem := pem.Block{
|
||||||
publicKeyDERHash := hasher.Sum(nil)
|
Type: "RSA PRIVATE KEY",
|
||||||
|
Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
|
||||||
keyID := base64.RawURLEncoding.EncodeToString(publicKeyDERHash)
|
}
|
||||||
|
privPemFile, err := os.Create("private_key.pem")
|
||||||
return keyID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type JWK struct {
|
|
||||||
Keys []jose.JSONWebKey `json:"keys"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewJWK(pub []byte) (*JWK, error){
|
|
||||||
pubKeys, err := keyutil.ParsePublicKeysPEM(pub)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
pubKey := pubKeys[0]
|
defer privPemFile.Close()
|
||||||
var alg jose.SignatureAlgorithm
|
if err := pem.Encode(privPemFile, &privPem); err != nil {
|
||||||
switch pubKey.(type) {
|
return err
|
||||||
case *rsa.PublicKey:
|
|
||||||
alg = jose.RS256
|
|
||||||
default:
|
|
||||||
return nil, errors.New("public key is not RSA")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
kid, err := keyIDFromPublicKey(pubKey)
|
// 公開鍵をPKIX, ASN.1 DER形式に変換
|
||||||
|
pubASN1, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var keys []jose.JSONWebKey
|
// 公開鍵をPEM形式で保存
|
||||||
keys = append(keys, jose.JSONWebKey{
|
pubPem := pem.Block{
|
||||||
Key: pubKey,
|
Type: "PUBLIC KEY",
|
||||||
KeyID: kid,
|
Bytes: pubASN1,
|
||||||
Algorithm: string(alg),
|
}
|
||||||
Use: "sig",
|
pubPemFile, err := os.Create("public_key.pem")
|
||||||
})
|
if err != nil {
|
||||||
return &JWK{Keys: keys}, nil
|
return err
|
||||||
|
}
|
||||||
|
defer pubPemFile.Close()
|
||||||
|
if err := pem.Encode(pubPemFile, &pubPem); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1 +1,35 @@
|
|||||||
package selfhosted
|
package selfhosted
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type OIDCIdProvider interface {
|
||||||
|
Discovery() string
|
||||||
|
JWK() string
|
||||||
|
Endpoint() string
|
||||||
|
}
|
||||||
|
type OIDCIdPCreator interface {
|
||||||
|
Upload(o OIDCIdProvider) error
|
||||||
|
CreateProvider() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type S3IdPCreator struct {
|
||||||
|
region string
|
||||||
|
bucketName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3IdPCreator) CreateProvider() error {
|
||||||
|
// create S3 Bucket // bucketName, Region
|
||||||
|
// create OIDCProvider //
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3IdPCreator) Upload(o OIDCIdProvider) error {
|
||||||
|
o.Discovery() // Upload to Endpoint()/.well-known/openid-configuration
|
||||||
|
o.JWK() // Upload to Endpoint()/keys.json
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *S3IdPCreator) issuerHostPath() string {
|
||||||
|
hostName := fmt.Sprintf("s3-%s.amazonaws.com", s.region)
|
||||||
|
return fmt.Sprintf("%s/%s", hostName, s.bucketName)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user