Add initial implementation of impersonation proxy.
Signed-off-by: Margo Crawford <margaretc@vmware.com> Signed-off-by: Matt Moyer <moyerm@vmware.com>
This commit is contained in:
parent
e1d06ce4d8
commit
b6abb022f6
41
cmd/debug-make-impersonation-token/main.go
Normal file
41
cmd/debug-make-impersonation-token/main.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
authenticationv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/authentication/v1alpha1"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1"
|
||||
)
|
||||
|
||||
func main() {
|
||||
reqJSON, err := json.Marshal(&loginv1alpha1.TokenCredentialRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: os.Getenv("PINNIPED_TEST_CONCIERGE_NAMESPACE"),
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "TokenCredentialRequest",
|
||||
APIVersion: loginv1alpha1.GroupName + "/v1alpha1",
|
||||
},
|
||||
Spec: loginv1alpha1.TokenCredentialRequestSpec{
|
||||
Token: os.Getenv("PINNIPED_TEST_USER_TOKEN"),
|
||||
Authenticator: corev1.TypedLocalObjectReference{
|
||||
APIGroup: &authenticationv1alpha1.SchemeGroupVersion.Group,
|
||||
Kind: os.Getenv("PINNIPED_AUTHENTICATOR_KIND"),
|
||||
Name: os.Getenv("PINNIPED_AUTHENTICATOR_NAME"),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(base64.RawURLEncoding.EncodeToString(reqJSON))
|
||||
}
|
@ -28,6 +28,9 @@ rules:
|
||||
resources: [ securitycontextconstraints ]
|
||||
verbs: [ use ]
|
||||
resourceNames: [ nonroot ]
|
||||
- apiGroups: [""]
|
||||
resources: ["users", "groups"]
|
||||
verbs: ["impersonate"]
|
||||
---
|
||||
kind: ClusterRoleBinding
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
|
156
internal/concierge/impersonator/impersonator.go
Normal file
156
internal/concierge/impersonator/impersonator.go
Normal file
@ -0,0 +1,156 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package impersonator
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/transport"
|
||||
|
||||
"go.pinniped.dev/generated/1.20/apis/concierge/login"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
)
|
||||
|
||||
// allowedHeaders are the set of HTTP headers that are allowed to be forwarded through the impersonation proxy.
|
||||
//nolint: gochecknoglobals
|
||||
var allowedHeaders = []string{
|
||||
"Accept",
|
||||
"Accept-Encoding",
|
||||
"User-Agent",
|
||||
"Connection",
|
||||
"Upgrade",
|
||||
}
|
||||
|
||||
type Proxy struct {
|
||||
cache *authncache.Cache
|
||||
proxy *httputil.ReverseProxy
|
||||
log logr.Logger
|
||||
}
|
||||
|
||||
func New(cache *authncache.Cache, log logr.Logger) (*Proxy, error) {
|
||||
return newInternal(cache, log, rest.InClusterConfig)
|
||||
}
|
||||
|
||||
func newInternal(cache *authncache.Cache, log logr.Logger, getConfig func() (*rest.Config, error)) (*Proxy, error) {
|
||||
kubeconfig, err := getConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get in-cluster config: %w", err)
|
||||
}
|
||||
|
||||
serverURL, err := url.Parse(kubeconfig.Host)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not parse host URL from in-cluster config: %w", err)
|
||||
}
|
||||
|
||||
kubeTransportConfig, err := kubeconfig.TransportConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get in-cluster transport config: %w", err)
|
||||
}
|
||||
kubeTransportConfig.TLS.NextProtos = []string{"http/1.1"}
|
||||
|
||||
kubeRoundTripper, err := transport.New(kubeTransportConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not get in-cluster transport: %w", err)
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(serverURL)
|
||||
proxy.Transport = kubeRoundTripper
|
||||
|
||||
return &Proxy{
|
||||
cache: cache,
|
||||
proxy: proxy,
|
||||
log: log,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
log := p.log.WithValues(
|
||||
"url", r.URL.String(),
|
||||
"method", r.Method,
|
||||
)
|
||||
|
||||
tokenCredentialReq, err := extractToken(r)
|
||||
if err != nil {
|
||||
log.Error(err, "invalid token encoding")
|
||||
http.Error(w, "invalid token encoding", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
log = log.WithValues(
|
||||
"authenticator", tokenCredentialReq.Spec.Authenticator,
|
||||
"authenticatorNamespace", tokenCredentialReq.Namespace,
|
||||
)
|
||||
|
||||
userInfo, err := p.cache.AuthenticateTokenCredentialRequest(r.Context(), tokenCredentialReq)
|
||||
if err != nil {
|
||||
log.Error(err, "received invalid token")
|
||||
http.Error(w, "invalid token", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
if userInfo == nil {
|
||||
log.Info("received token that did not authenticate")
|
||||
http.Error(w, "not authenticated", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
log = log.WithValues(
|
||||
"user", userInfo.GetName(),
|
||||
"groups", userInfo.GetGroups(),
|
||||
)
|
||||
|
||||
newHeaders := getProxyHeaders(userInfo, r.Header)
|
||||
r.Header = newHeaders
|
||||
|
||||
log.Info("proxying authenticated request")
|
||||
p.proxy.ServeHTTP(w, r)
|
||||
}
|
||||
|
||||
func getProxyHeaders(userInfo user.Info, requestHeaders http.Header) http.Header {
|
||||
newHeaders := http.Header{}
|
||||
newHeaders.Set("Impersonate-User", userInfo.GetName())
|
||||
for _, group := range userInfo.GetGroups() {
|
||||
newHeaders.Add("Impersonate-Group", group)
|
||||
}
|
||||
for _, header := range allowedHeaders {
|
||||
values := requestHeaders.Values(header)
|
||||
for i := range values {
|
||||
newHeaders.Add(header, values[i])
|
||||
}
|
||||
}
|
||||
return newHeaders
|
||||
}
|
||||
|
||||
func extractToken(req *http.Request) (*login.TokenCredentialRequest, error) {
|
||||
authHeader := req.Header.Get("Authorization")
|
||||
if authHeader == "" {
|
||||
return nil, fmt.Errorf("missing authorization header")
|
||||
}
|
||||
if !strings.HasPrefix(authHeader, "Bearer ") {
|
||||
return nil, fmt.Errorf("authorization header must be of type Bearer")
|
||||
}
|
||||
encoded := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
tokenCredentialRequestJSON, err := base64.RawURLEncoding.DecodeString(encoded)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid base64 in encoded bearer token: %w", err)
|
||||
}
|
||||
|
||||
var v1alpha1Req loginv1alpha1.TokenCredentialRequest
|
||||
if err := json.Unmarshal(tokenCredentialRequestJSON, &v1alpha1Req); err != nil {
|
||||
return nil, fmt.Errorf("invalid TokenCredentialRequest encoded in bearer token: %w", err)
|
||||
}
|
||||
var internalReq login.TokenCredentialRequest
|
||||
if err := loginv1alpha1.Convert_v1alpha1_TokenCredentialRequest_To_login_TokenCredentialRequest(&v1alpha1Req, &internalReq, nil); err != nil {
|
||||
return nil, fmt.Errorf("failed to convert v1alpha1 TokenCredentialRequest to internal version: %w", err)
|
||||
}
|
||||
return &internalReq, nil
|
||||
}
|
259
internal/concierge/impersonator/impersonator_test.go
Normal file
259
internal/concierge/impersonator/impersonator_test.go
Normal file
@ -0,0 +1,259 @@
|
||||
// Copyright 2020-2021 the Pinniped contributors. All Rights Reserved.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package impersonator
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/require"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd/api"
|
||||
|
||||
loginv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/mocks/mocktokenauthenticator"
|
||||
"go.pinniped.dev/internal/testutil"
|
||||
"go.pinniped.dev/internal/testutil/testlogger"
|
||||
)
|
||||
|
||||
func TestImpersonator(t *testing.T) {
|
||||
validURL, _ := url.Parse("http://pinniped.dev/blah")
|
||||
testServerCA, testServerURL := testutil.TLSTestServer(t, func(w http.ResponseWriter, r *http.Request) {
|
||||
// Expect that the request is authenticated based on the kubeconfig credential.
|
||||
if r.Header.Get("Authorization") != "Bearer some-service-account-token" {
|
||||
http.Error(w, "expected to see service account token", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// Fail if we see the malicious header passed through the proxy (it's not on the allowlist).
|
||||
if r.Header.Get("Malicious-Header") != "" {
|
||||
http.Error(w, "didn't expect to see malicious header", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
// Expect to see the user agent header passed through.
|
||||
if r.Header.Get("User-Agent") != "test-user-agent" {
|
||||
http.Error(w, "got unexpected user agent header", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, _ = w.Write([]byte("successful proxied response"))
|
||||
})
|
||||
testServerKubeconfig := rest.Config{
|
||||
Host: testServerURL,
|
||||
BearerToken: "some-service-account-token",
|
||||
TLSClientConfig: rest.TLSClientConfig{CAData: []byte(testServerCA)},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
getKubeconfig func() (*rest.Config, error)
|
||||
wantCreationErr string
|
||||
request *http.Request
|
||||
wantHTTPBody string
|
||||
wantHTTPStatus int
|
||||
wantLogs []string
|
||||
expectMockToken func(*testing.T, *mocktokenauthenticator.MockTokenMockRecorder)
|
||||
}{
|
||||
{
|
||||
name: "fail to get in-cluster config",
|
||||
getKubeconfig: func() (*rest.Config, error) {
|
||||
return nil, fmt.Errorf("some kubernetes error")
|
||||
},
|
||||
wantCreationErr: "could not get in-cluster config: some kubernetes error",
|
||||
},
|
||||
{
|
||||
name: "invalid kubeconfig host",
|
||||
getKubeconfig: func() (*rest.Config, error) {
|
||||
return &rest.Config{Host: ":"}, nil
|
||||
},
|
||||
wantCreationErr: "could not parse host URL from in-cluster config: parse \":\": missing protocol scheme",
|
||||
},
|
||||
{
|
||||
name: "invalid transport config",
|
||||
getKubeconfig: func() (*rest.Config, error) {
|
||||
return &rest.Config{
|
||||
Host: "pinniped.dev/blah",
|
||||
ExecProvider: &api.ExecConfig{},
|
||||
AuthProvider: &api.AuthProviderConfig{},
|
||||
}, nil
|
||||
},
|
||||
wantCreationErr: "could not get in-cluster transport config: execProvider and authProvider cannot be used in combination",
|
||||
},
|
||||
{
|
||||
name: "fail to get transport from config",
|
||||
getKubeconfig: func() (*rest.Config, error) {
|
||||
return &rest.Config{
|
||||
Host: "pinniped.dev/blah",
|
||||
BearerToken: "test-bearer-token",
|
||||
Transport: http.DefaultTransport,
|
||||
TLSClientConfig: rest.TLSClientConfig{Insecure: true},
|
||||
}, nil
|
||||
},
|
||||
wantCreationErr: "could not get in-cluster transport: using a custom transport with TLS certificate options or the insecure flag is not allowed",
|
||||
},
|
||||
{
|
||||
name: "missing authorization header",
|
||||
getKubeconfig: func() (*rest.Config, error) { return &testServerKubeconfig, nil },
|
||||
request: &http.Request{
|
||||
Method: "GET",
|
||||
Header: map[string][]string{},
|
||||
URL: validURL,
|
||||
},
|
||||
wantHTTPBody: "invalid token encoding\n",
|
||||
wantHTTPStatus: http.StatusBadRequest,
|
||||
wantLogs: []string{"\"error\"=\"missing authorization header\" \"msg\"=\"invalid token encoding\" \"method\"=\"GET\" \"url\"=\"http://pinniped.dev/blah\""},
|
||||
},
|
||||
{
|
||||
name: "authorization header missing bearer prefix",
|
||||
getKubeconfig: func() (*rest.Config, error) { return &testServerKubeconfig, nil },
|
||||
request: &http.Request{
|
||||
Method: "GET",
|
||||
Header: map[string][]string{"Authorization": {makeTestTokenRequest("foo", "authenticator-one", "test-token")}},
|
||||
URL: validURL,
|
||||
},
|
||||
wantHTTPBody: "invalid token encoding\n",
|
||||
wantHTTPStatus: http.StatusBadRequest,
|
||||
wantLogs: []string{"\"error\"=\"authorization header must be of type Bearer\" \"msg\"=\"invalid token encoding\" \"method\"=\"GET\" \"url\"=\"http://pinniped.dev/blah\""},
|
||||
},
|
||||
{
|
||||
name: "token is not base64 encoded",
|
||||
getKubeconfig: func() (*rest.Config, error) { return &testServerKubeconfig, nil },
|
||||
request: &http.Request{
|
||||
Method: "GET",
|
||||
Header: map[string][]string{"Authorization": {"Bearer !!!"}},
|
||||
URL: validURL,
|
||||
},
|
||||
wantHTTPBody: "invalid token encoding\n",
|
||||
wantHTTPStatus: http.StatusBadRequest,
|
||||
wantLogs: []string{"\"error\"=\"invalid base64 in encoded bearer token: illegal base64 data at input byte 0\" \"msg\"=\"invalid token encoding\" \"method\"=\"GET\" \"url\"=\"http://pinniped.dev/blah\""},
|
||||
},
|
||||
{
|
||||
name: "base64 encoded token is not valid json",
|
||||
getKubeconfig: func() (*rest.Config, error) { return &testServerKubeconfig, nil },
|
||||
request: &http.Request{
|
||||
Method: "GET",
|
||||
Header: map[string][]string{"Authorization": {"Bearer abc"}},
|
||||
URL: validURL,
|
||||
},
|
||||
wantHTTPBody: "invalid token encoding\n",
|
||||
wantHTTPStatus: http.StatusBadRequest,
|
||||
wantLogs: []string{"\"error\"=\"invalid TokenCredentialRequest encoded in bearer token: invalid character 'i' looking for beginning of value\" \"msg\"=\"invalid token encoding\" \"method\"=\"GET\" \"url\"=\"http://pinniped.dev/blah\""},
|
||||
},
|
||||
{
|
||||
name: "token could not be authenticated",
|
||||
getKubeconfig: func() (*rest.Config, error) { return &testServerKubeconfig, nil },
|
||||
request: &http.Request{
|
||||
Method: "GET",
|
||||
Header: map[string][]string{"Authorization": {"Bearer " + makeTestTokenRequest("", "", "")}},
|
||||
URL: validURL,
|
||||
},
|
||||
wantHTTPBody: "invalid token\n",
|
||||
wantHTTPStatus: http.StatusUnauthorized,
|
||||
wantLogs: []string{"\"error\"=\"no such authenticator\" \"msg\"=\"received invalid token\" \"authenticator\"={\"apiGroup\":null,\"kind\":\"\",\"name\":\"\"} \"authenticatorNamespace\"=\"\" \"method\"=\"GET\" \"url\"=\"http://pinniped.dev/blah\""},
|
||||
},
|
||||
{
|
||||
name: "token authenticates as nil",
|
||||
getKubeconfig: func() (*rest.Config, error) { return &testServerKubeconfig, nil },
|
||||
request: &http.Request{
|
||||
Method: "GET",
|
||||
Header: map[string][]string{"Authorization": {"Bearer " + makeTestTokenRequest("foo", "authenticator-one", "test-token")}},
|
||||
URL: validURL,
|
||||
},
|
||||
expectMockToken: func(t *testing.T, recorder *mocktokenauthenticator.MockTokenMockRecorder) {
|
||||
recorder.AuthenticateToken(gomock.Any(), "test-token").Return(nil, false, nil)
|
||||
},
|
||||
wantHTTPBody: "not authenticated\n",
|
||||
wantHTTPStatus: http.StatusUnauthorized,
|
||||
wantLogs: []string{"\"level\"=0 \"msg\"=\"received token that did not authenticate\" \"authenticator\"={\"apiGroup\":null,\"kind\":\"\",\"name\":\"authenticator-one\"} \"authenticatorNamespace\"=\"foo\" \"method\"=\"GET\" \"url\"=\"http://pinniped.dev/blah\""},
|
||||
},
|
||||
// happy path
|
||||
{
|
||||
name: "token validates",
|
||||
getKubeconfig: func() (*rest.Config, error) { return &testServerKubeconfig, nil },
|
||||
request: &http.Request{
|
||||
Method: "GET",
|
||||
Header: map[string][]string{
|
||||
"Authorization": {"Bearer " + makeTestTokenRequest("foo", "authenticator-one", "test-token")},
|
||||
"Malicious-Header": {"test-header-value-1"},
|
||||
"User-Agent": {"test-user-agent"},
|
||||
},
|
||||
URL: validURL,
|
||||
},
|
||||
expectMockToken: func(t *testing.T, recorder *mocktokenauthenticator.MockTokenMockRecorder) {
|
||||
userInfo := user.DefaultInfo{Name: "test-user", Groups: []string{"test-group-1", "test-group-2"}}
|
||||
response := &authenticator.Response{User: &userInfo}
|
||||
recorder.AuthenticateToken(gomock.Any(), "test-token").Return(response, true, nil)
|
||||
},
|
||||
wantHTTPBody: "successful proxied response",
|
||||
wantHTTPStatus: http.StatusOK,
|
||||
wantLogs: []string{"\"level\"=0 \"msg\"=\"proxying authenticated request\" \"authenticator\"={\"apiGroup\":null,\"kind\":\"\",\"name\":\"authenticator-one\"} \"authenticatorNamespace\"=\"foo\" \"groups\"=[\"test-group-1\",\"test-group-2\"] \"method\"=\"GET\" \"url\"=\"http://pinniped.dev/blah\" \"user\"=\"test-user\""},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
tt := tt
|
||||
testLog := testlogger.New(t)
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// stole this from cache_test, hopefully it is sufficient
|
||||
cacheWithMockAuthenticator := authncache.New()
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
key := authncache.Key{Namespace: "foo", Name: "authenticator-one"}
|
||||
mockToken := mocktokenauthenticator.NewMockToken(ctrl)
|
||||
cacheWithMockAuthenticator.Store(key, mockToken)
|
||||
|
||||
if tt.expectMockToken != nil {
|
||||
tt.expectMockToken(t, mockToken.EXPECT())
|
||||
}
|
||||
|
||||
proxy, err := newInternal(cacheWithMockAuthenticator, testLog, tt.getKubeconfig)
|
||||
if tt.wantCreationErr != "" {
|
||||
require.EqualError(t, err, tt.wantCreationErr)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, proxy)
|
||||
w := httptest.NewRecorder()
|
||||
proxy.ServeHTTP(w, tt.request)
|
||||
if tt.wantHTTPStatus != 0 {
|
||||
require.Equal(t, tt.wantHTTPStatus, w.Code)
|
||||
}
|
||||
if tt.wantHTTPBody != "" {
|
||||
require.Equal(t, tt.wantHTTPBody, w.Body.String())
|
||||
}
|
||||
if tt.wantLogs != nil {
|
||||
require.Equal(t, tt.wantLogs, testLog.Lines())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeTestTokenRequest(namespace string, name string, token string) string {
|
||||
reqJSON, err := json.Marshal(&loginv1alpha1.TokenCredentialRequest{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
},
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "TokenCredentialRequest",
|
||||
APIVersion: loginv1alpha1.GroupName + "/v1alpha1",
|
||||
},
|
||||
Spec: loginv1alpha1.TokenCredentialRequestSpec{
|
||||
Token: token,
|
||||
Authenticator: corev1.TypedLocalObjectReference{Name: name},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return base64.RawURLEncoding.EncodeToString(reqJSON)
|
||||
}
|
@ -6,8 +6,11 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509/pkix"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
@ -18,11 +21,15 @@ import (
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/klog/v2/klogr"
|
||||
|
||||
loginapi "go.pinniped.dev/generated/1.20/apis/concierge/login"
|
||||
loginv1alpha1 "go.pinniped.dev/generated/1.20/apis/concierge/login/v1alpha1"
|
||||
"go.pinniped.dev/internal/certauthority"
|
||||
"go.pinniped.dev/internal/certauthority/dynamiccertauthority"
|
||||
"go.pinniped.dev/internal/concierge/apiserver"
|
||||
"go.pinniped.dev/internal/concierge/impersonator"
|
||||
"go.pinniped.dev/internal/config/concierge"
|
||||
"go.pinniped.dev/internal/controller/authenticator/authncache"
|
||||
"go.pinniped.dev/internal/controllermanager"
|
||||
@ -162,6 +169,34 @@ func (a *App) runServer(ctx context.Context) error {
|
||||
return fmt.Errorf("could not create aggregated API server: %w", err)
|
||||
}
|
||||
|
||||
// run proxy handler
|
||||
impersonationCA, err := certauthority.New(pkix.Name{CommonName: "test CA"}, 24*time.Hour)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create impersonation CA: %w", err)
|
||||
}
|
||||
impersonationCert, err := impersonationCA.Issue(pkix.Name{}, []string{"impersonation-proxy"}, nil, 24*time.Hour)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create impersonation cert: %w", err)
|
||||
}
|
||||
impersonationProxy, err := impersonator.New(authenticators, klogr.New().WithName("impersonation-proxy"))
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create impersonation proxy: %w", err)
|
||||
}
|
||||
|
||||
impersonationProxyServer := http.Server{
|
||||
Addr: "0.0.0.0:8444",
|
||||
Handler: impersonationProxy,
|
||||
TLSConfig: &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
Certificates: []tls.Certificate{*impersonationCert},
|
||||
},
|
||||
}
|
||||
go func() {
|
||||
if err := impersonationProxyServer.ListenAndServeTLS("", ""); err != nil {
|
||||
klog.ErrorS(err, "could not serve impersonation proxy")
|
||||
}
|
||||
}()
|
||||
|
||||
// Run the server. Its post-start hook will start the controllers.
|
||||
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
||||
}
|
||||
|
16
proxy-kubeconfig.yaml
Normal file
16
proxy-kubeconfig.yaml
Normal file
@ -0,0 +1,16 @@
|
||||
apiVersion: v1
|
||||
clusters:
|
||||
- cluster:
|
||||
server: https://127.0.0.1:8444
|
||||
insecure-skip-tls-verify: true
|
||||
name: kind-pinniped
|
||||
contexts:
|
||||
- context:
|
||||
cluster: kind-pinniped
|
||||
user: kind-pinniped
|
||||
name: kind-pinniped
|
||||
current-context: kind-pinniped
|
||||
kind: Config
|
||||
preferences: {}
|
||||
users:
|
||||
- name: kind-pinniped
|
Loading…
Reference in New Issue
Block a user