Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ tasks:
## Testing
fmt:
cmds:
- go fmt ./...
- "{{.LOCAL_BIN}}/golangci-lint fmt ./..."
lint:
deps: [setup:golangci-lint]
Expand Down
3 changes: 1 addition & 2 deletions cmd/listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ import (
)

var listenCmd = &cobra.Command{
Use: "listener",
Example: "KUBECONFIG=<path to kubeconfig file> go run . listener",
Use: "listener",
Run: func(cmd *cobra.Command, args []string) {
log.Info().Str("LogLevel", log.GetLevel().String()).Msg("Starting the Listener...")

Expand Down
98 changes: 19 additions & 79 deletions common/auth/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,24 @@ import (
"encoding/base64"
"errors"
"fmt"
"time"

gatewayv1alpha1 "github.com/platform-mesh/kubernetes-graphql-gateway/common/apis/v1alpha1"

authv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/clientcmd/api"
"k8s.io/utils/ptr"
"sigs.k8s.io/controller-runtime/pkg/client"
)

// BuildConfig creates a rest.Config from cluster connection parameters
// This function unifies the authentication logic used by both listener and gateway
func BuildConfig(ctx context.Context, host string, auth *gatewayv1alpha1.AuthConfig, ca *gatewayv1alpha1.CAConfig, k8sClient client.Client) (*rest.Config, error) {
if host == "" {
return nil, errors.New("host is required")
}

func BuildConfig(ctx context.Context, auth *gatewayv1alpha1.AuthConfig, ca *gatewayv1alpha1.CAConfig, k8sClient client.Client) (*rest.Config, error) {
config := &rest.Config{
Host: host,
TLSClientConfig: rest.TLSClientConfig{
Insecure: true, // Start with insecure, will be overridden if CA is provided
},
Expand Down Expand Up @@ -61,7 +58,6 @@ func BuildConfigFromMetadata(host string, authType, token, kubeconfig, certData,
}

config := &rest.Config{
Host: host,
TLSClientConfig: rest.TLSClientConfig{
Insecure: true, // Start with insecure, will be overridden if CA is provided
},
Expand Down Expand Up @@ -113,6 +109,8 @@ func BuildConfigFromMetadata(host string, authType, token, kubeconfig, certData,
}
}

config.Host = host

return config, nil
}

Expand Down Expand Up @@ -252,36 +250,28 @@ func ConfigureAuthentication(ctx context.Context, config *rest.Config, auth *gat
}

if auth.ServiceAccount != nil {
var expirationSeconds int64
expiration := 1 * time.Hour
if auth.ServiceAccount.TokenExpiration != nil {
// If TokenExpiration is provided, use its value
expirationSeconds = int64(auth.ServiceAccount.TokenExpiration.Seconds())
} else {
// If TokenExpiration is nil, use the desired default (3600 seconds = 1 hour)
expirationSeconds = 3600
fmt.Println("Warning: auth.ServiceAccount.TokenExpiration is nil, defaulting to 3600 seconds.")
expiration = auth.ServiceAccount.TokenExpiration.Duration
}

// Build the TokenRequest object
tokenRequest := &authv1.TokenRequest{
Spec: authv1.TokenRequestSpec{
Audiences: auth.ServiceAccount.Audience,
// Optionally set ExpirationSeconds, BoundObjectRef, etc.
ExpirationSeconds: &expirationSeconds,
Audiences: auth.ServiceAccount.Audience,
ExpirationSeconds: ptr.To(int64(expiration.Seconds())),
},
}

// Get the service account token using the Kubernetes API
sa := &corev1.ServiceAccount{}
err := k8sClient.Get(ctx, types.NamespacedName{
Name: auth.ServiceAccount.Name,
Namespace: auth.ServiceAccount.Namespace,
}, sa)
if err != nil {
return errors.Join(errors.New("failed to get service account"), err)
sa := &corev1.ServiceAccount{
ObjectMeta: metav1.ObjectMeta{
Name: auth.ServiceAccount.Name,
Namespace: auth.ServiceAccount.Namespace,
},
}

err = k8sClient.SubResource("token").Create(ctx, sa, tokenRequest)
err := k8sClient.SubResource("token").Create(ctx, sa, tokenRequest)
if err != nil {
return errors.Join(errors.New("failed to create token request for service account"), err)
}
Expand All @@ -306,62 +296,12 @@ func ConfigureFromKubeconfig(config *rest.Config, kubeconfigData []byte) error {
return errors.Join(errors.New("failed to parse kubeconfig"), err)
}

rawConfig, err := clientConfig.RawConfig()
extracted, err := clientConfig.ClientConfig()
if err != nil {
return errors.Join(errors.New("failed to get raw kubeconfig"), err)
}

// Get the current context
currentContext := rawConfig.CurrentContext
if currentContext == "" {
return errors.New("no current context in kubeconfig")
}

context, exists := rawConfig.Contexts[currentContext]
if !exists {
return errors.New("current context not found in kubeconfig")
}

// Get auth info for current context
authInfo, exists := rawConfig.AuthInfos[context.AuthInfo]
if !exists {
return errors.New("auth info not found in kubeconfig")
}

// Extract authentication information
return ExtractAuthFromKubeconfig(config, authInfo)
}

// ExtractAuthFromKubeconfig extracts authentication info from kubeconfig AuthInfo
func ExtractAuthFromKubeconfig(config *rest.Config, authInfo *api.AuthInfo) error {
if authInfo.Token != "" {
config.BearerToken = authInfo.Token
return nil
}

if authInfo.TokenFile != "" {
// TODO: Read token from file if needed
return errors.New("token file authentication not yet implemented")
}

if len(authInfo.ClientCertificateData) > 0 && len(authInfo.ClientKeyData) > 0 {
config.CertData = authInfo.ClientCertificateData
config.KeyData = authInfo.ClientKeyData
return nil
}
*config = *extracted

if authInfo.ClientCertificate != "" && authInfo.ClientKey != "" {
config.CertFile = authInfo.ClientCertificate
config.KeyFile = authInfo.ClientKey
return nil
}

if authInfo.Username != "" && authInfo.Password != "" {
config.Username = authInfo.Username
config.Password = authInfo.Password
return nil
}

// No recognizable authentication found
return errors.New("no valid authentication method found in kubeconfig")
return nil
}
110 changes: 0 additions & 110 deletions common/auth/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd/api"
"sigs.k8s.io/controller-runtime/pkg/client"
)

Expand Down Expand Up @@ -271,112 +270,3 @@ clusters:
})
}
}

func TestExtractAuthFromKubeconfig(t *testing.T) {
tests := []struct {
name string
authInfo *api.AuthInfo
wantConfig func(*rest.Config) *rest.Config
wantErr bool
errContains string
}{
{
name: "token_auth",
authInfo: &api.AuthInfo{
Token: "test-token",
},
wantConfig: func(config *rest.Config) *rest.Config {
expected := *config
expected.BearerToken = "test-token"
return &expected
},
wantErr: false,
},
{
name: "client_certificate_data",
authInfo: &api.AuthInfo{
ClientCertificateData: []byte("cert-data"),
ClientKeyData: []byte("key-data"),
},
wantConfig: func(config *rest.Config) *rest.Config {
expected := *config
expected.CertData = []byte("cert-data")
expected.KeyData = []byte("key-data")
return &expected
},
wantErr: false,
},
{
name: "client_certificate_files",
authInfo: &api.AuthInfo{
ClientCertificate: "/path/to/cert.pem",
ClientKey: "/path/to/key.pem",
},
wantConfig: func(config *rest.Config) *rest.Config {
expected := *config
expected.CertFile = "/path/to/cert.pem"
expected.KeyFile = "/path/to/key.pem"
return &expected
},
wantErr: false,
},
{
name: "basic_auth",
authInfo: &api.AuthInfo{
Username: "test-user",
Password: "test-password",
},
wantConfig: func(config *rest.Config) *rest.Config {
expected := *config
expected.Username = "test-user"
expected.Password = "test-password"
return &expected
},
wantErr: false,
},
{
name: "token_file_not_implemented",
authInfo: &api.AuthInfo{
TokenFile: "/path/to/token",
},
wantConfig: func(config *rest.Config) *rest.Config {
return config
},
wantErr: true,
errContains: "token file authentication not yet implemented",
},
{
name: "no_auth_info",
authInfo: &api.AuthInfo{},
wantConfig: func(config *rest.Config) *rest.Config {
return config
},
wantErr: true,
errContains: "no valid authentication method found in kubeconfig",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
config := &rest.Config{
Host: "https://test.example.com",
TLSClientConfig: rest.TLSClientConfig{
Insecure: true,
},
}

err := ExtractAuthFromKubeconfig(config, tt.authInfo)

if tt.wantErr {
assert.Error(t, err)
if tt.errContains != "" {
assert.Contains(t, err.Error(), tt.errContains)
}
} else {
assert.NoError(t, err)
expected := tt.wantConfig(config)
assert.Equal(t, expected, config)
}
})
}
}
4 changes: 0 additions & 4 deletions docs/assets/Listener_High_Level.drawio.svg

This file was deleted.

34 changes: 0 additions & 34 deletions docs/authorization.md

This file was deleted.

Loading
Loading