Initial commit

[ZITADOPER-1]
This commit is contained in:
Haim Kortovich
2024-04-15 14:44:46 -05:00
parent 95e7d1cb69
commit e4eef2928a
121 changed files with 9053 additions and 0 deletions

166
src/pkg/zitadel/zitadel.go Normal file
View File

@@ -0,0 +1,166 @@
package zitadel
import (
zitadelv1alpha1 "bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/api/v1alpha1"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/deployment"
"bitbucket.org/topmanage-software-engineering/zitadel-k8s-operator/src/pkg/machinekey"
"context"
"encoding/json"
"fmt"
"github.com/gorilla/schema"
"google.golang.org/grpc"
"net/http"
"reflect"
"strings"
"time"
"github.com/zitadel/oidc/pkg/client"
httphelper "github.com/zitadel/oidc/pkg/http"
"github.com/zitadel/oidc/pkg/oidc"
"github.com/zitadel/zitadel-go/v2/pkg/client/management"
"github.com/zitadel/zitadel-go/v2/pkg/client/zitadel"
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2"
corev1 "k8s.io/api/core/v1"
)
type MachineKey struct {
Type string `json:"type"`
KeyID string `json:"keyId"`
Key string `json:"key"`
UserID string `json:"userId"`
}
func NewClient(ctx context.Context, zitadelCluster *zitadelv1alpha1.ZitadelCluster, refresolver zitadelv1alpha1.RefResolver) (*management.Client, error) {
machineKeyData, err := refresolver.SecretKeyRef(ctx, corev1.SecretKeySelector{LocalObjectReference: corev1.LocalObjectReference{Name: machinekey.MachineKeySecretName(zitadelCluster)}, Key: machinekey.Key}, zitadelCluster.Namespace)
if err != nil {
return nil, err
}
api, err := management.NewClient(GetIssuer(zitadelCluster), fmt.Sprintf("%s", GetAPI(zitadelCluster)), []string{oidc.ScopeOpenID, zitadel.ScopeZitadelAPI()}, zitadel.WithInsecure(), zitadel.WithJWTProfileTokenSource(Discover([]byte(machineKeyData), GetAPIUrl(zitadelCluster), GetAuthority(zitadelCluster), GetAPI(zitadelCluster))),
zitadel.WithDialOptions(grpc.WithAuthority(GetAuthority(zitadelCluster))),
)
if err != nil {
return nil, fmt.Errorf("ERROR CREATING CLIENT: %v", err)
}
return api, nil
}
func GetAuthority(zitadel *zitadelv1alpha1.ZitadelCluster) string {
return fmt.Sprintf("%s:%d", zitadel.Spec.Host, zitadel.Spec.ExternalPort)
}
func GetIssuer(zitadel *zitadelv1alpha1.ZitadelCluster) string {
return fmt.Sprintf("http://%s:%d", zitadel.Spec.Host, zitadel.Spec.ExternalPort)
}
func GetAPI(zitadel *zitadelv1alpha1.ZitadelCluster) string {
return fmt.Sprintf("%s:%d", deployment.ServiceFQDN(zitadel.ObjectMeta), deployment.ZitadelPort)
}
func GetAPIUrl(zitadel *zitadelv1alpha1.ZitadelCluster) string {
return fmt.Sprintf("http://%s:%d", deployment.ServiceFQDN(zitadel.ObjectMeta), deployment.ZitadelPort)
}
type jwtProfileTokenSource struct {
clientID string
audience []string
signer jose.Signer
scopes []string
httpClient *http.Client
tokenEndpoint string
host string
}
func Discover(key []byte, discoverUrl string, host string, api string) func(issuer string, scopes []string) (oauth2.TokenSource, error) {
return func(issuer string, scopes []string) (oauth2.TokenSource, error) {
var machineKeyData MachineKey
if err := json.Unmarshal(key, &machineKeyData); err != nil {
return nil, err
}
signer, err := client.NewSignerFromPrivateKeyByte([]byte(machineKeyData.Key), machineKeyData.KeyID)
if err != nil {
return nil, err
}
source := &jwtProfileTokenSource{
host: host,
clientID: machineKeyData.UserID,
audience: []string{issuer},
signer: signer,
scopes: scopes,
httpClient: http.DefaultClient,
}
config, err := GetDiscoveryConfig(discoverUrl, http.DefaultClient, host, api)
if err != nil {
return nil, err
}
source.tokenEndpoint = config.TokenEndpoint
return source, nil
}
}
func GetDiscoveryConfig(issuer string, httpClient *http.Client, host string, api string, wellKnownUrl ...string) (*oidc.DiscoveryConfiguration, error) {
wellKnown := strings.TrimSuffix(issuer, "/") + oidc.DiscoveryEndpoint
if len(wellKnownUrl) == 1 && wellKnownUrl[0] != "" {
wellKnown = wellKnownUrl[0]
}
req, err := http.NewRequest("GET", wellKnown, nil)
if err != nil {
return nil, err
}
req.Host = host
discoveryConfig := new(oidc.DiscoveryConfiguration)
err = httphelper.HttpRequest(httpClient, req, &discoveryConfig)
discoveryConfig.TokenEndpoint = strings.ReplaceAll(discoveryConfig.TokenEndpoint, host, api)
if err != nil {
return nil, err
}
return discoveryConfig, nil
}
func (j *jwtProfileTokenSource) TokenEndpoint() string {
return j.tokenEndpoint
}
func (j *jwtProfileTokenSource) HttpClient() *http.Client {
return j.httpClient
}
func (j *jwtProfileTokenSource) Token() (*oauth2.Token, error) {
assertion, err := client.SignedJWTProfileAssertion(j.clientID, j.audience, time.Hour, j.signer)
if err != nil {
return nil, err
}
token, err := callTokenEndpoint(oidc.NewJWTProfileGrantRequest(assertion, j.scopes...), nil, j, j.host)
if err != nil {
return nil, err
}
return token, err
}
var Encoder = func() httphelper.Encoder {
e := schema.NewEncoder()
e.RegisterEncoder(oidc.SpaceDelimitedArray{}, func(value reflect.Value) string {
return value.Interface().(oidc.SpaceDelimitedArray).Encode()
})
return e
}()
func callTokenEndpoint(request interface{}, authFn interface{}, caller client.TokenEndpointCaller, host string) (newToken *oauth2.Token, err error) {
req, err := httphelper.FormRequest(caller.TokenEndpoint(), request, Encoder, authFn)
if err != nil {
return nil, err
}
tokenRes := new(oidc.AccessTokenResponse)
req.Host = host
if err := httphelper.HttpRequest(caller.HttpClient(), req, &tokenRes); err != nil {
return nil, err
}
return &oauth2.Token{
AccessToken: tokenRes.AccessToken,
TokenType: tokenRes.TokenType,
RefreshToken: tokenRes.RefreshToken,
Expiry: time.Now().UTC().Add(time.Duration(tokenRes.ExpiresIn) * time.Second),
}, nil
}