Skip to content

Commit e927ec5

Browse files
feat: introduce option for API to use SelfSubjectReview
1 parent fb86e28 commit e927ec5

File tree

4 files changed

+144
-25
lines changed

4 files changed

+144
-25
lines changed

authentication/openshift.go

Lines changed: 28 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ type openshiftAuthenticatorConfig struct {
6363
ServiceAccount string `json:"serviceAccount"`
6464
RedirectURL string `json:"redirectURL"`
6565
CookieSecret string `json:"cookieSecret"`
66+
SSREnabled bool `json:"ssrEnabled"`
6667
ServiceAccountCA []byte
6768
}
6869

@@ -175,32 +176,35 @@ func newOpenshiftAuthenticator(c map[string]interface{}, tenant string,
175176
break
176177
}
177178

178-
authOpts := openshift.DelegatingAuthenticationOptions{
179-
RemoteKubeConfigFile: config.KubeConfigPath,
180-
CacheTTL: 2 * time.Minute,
181-
ClientCert: openshift.ClientCertAuthenticationOptions{
182-
ClientCA: openshift.ServiceAccountCAPath,
183-
},
184-
RequestHeader: openshift.RequestHeaderAuthenticationOptions{
185-
ClientCAFile: openshift.ServiceAccountCAPath,
186-
},
187-
SkipInClusterLookup: true,
188-
WebhookRetryBackoff: &wait.Backoff{ // Default APIserver options
189-
Duration: 500 * time.Millisecond,
190-
Factor: 1.5,
191-
Jitter: 0.2,
192-
Steps: 5,
193-
},
194-
}
179+
authenticator := openshift.NewSelfSubjectReview(config.KubeConfigPath, logger)
180+
if !config.SSREnabled {
181+
authOpts := openshift.DelegatingAuthenticationOptions{
182+
RemoteKubeConfigFile: config.KubeConfigPath,
183+
CacheTTL: 2 * time.Minute,
184+
ClientCert: openshift.ClientCertAuthenticationOptions{
185+
ClientCA: openshift.ServiceAccountCAPath,
186+
},
187+
RequestHeader: openshift.RequestHeaderAuthenticationOptions{
188+
ClientCAFile: openshift.ServiceAccountCAPath,
189+
},
190+
SkipInClusterLookup: true,
191+
WebhookRetryBackoff: &wait.Backoff{ // Default APIserver options
192+
Duration: 500 * time.Millisecond,
193+
Factor: 1.5,
194+
Jitter: 0.2,
195+
Steps: 5,
196+
},
197+
}
195198

196-
authConfig, err := authOpts.ToAuthenticationConfig()
197-
if err != nil {
198-
return nil, errors.Wrap(err, "unable to create auth config")
199-
}
199+
authConfig, err := authOpts.ToAuthenticationConfig()
200+
if err != nil {
201+
return nil, errors.Wrap(err, "unable to create auth config")
202+
}
200203

201-
authenticator, _, err := authConfig.New()
202-
if err != nil {
203-
return nil, errors.Wrap(err, "unable to initialize authenticator")
204+
authenticator, _, err = authConfig.New()
205+
if err != nil {
206+
return nil, errors.Wrap(err, "unable to initialize authenticator")
207+
}
204208
}
205209

206210
var cipher *openshift.Cipher
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package openshift
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
"os"
8+
"os/user"
9+
"path"
10+
"strings"
11+
12+
"github.com/go-kit/log"
13+
"github.com/go-kit/log/level"
14+
15+
authenticationv1 "k8s.io/api/authentication/v1"
16+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17+
"k8s.io/apiserver/pkg/authentication/authenticator"
18+
k8suser "k8s.io/apiserver/pkg/authentication/user"
19+
"k8s.io/client-go/kubernetes"
20+
"k8s.io/client-go/rest"
21+
"k8s.io/client-go/tools/clientcmd"
22+
)
23+
24+
// SelfSubjectReview is a struct that implements the Token and Request interfaces.
25+
type SelfSubjectReview struct {
26+
logger log.Logger
27+
// RemoteKubeConfigFile is the file to use to connect to a "normal" kube API server which hosts the
28+
// TokenAccessReview.authentication.k8s.io endpoint for checking tokens.
29+
RemoteKubeConfigFile string
30+
}
31+
32+
// NewSelfSubjectReview creates a new instance of SelfSubjectReview.
33+
func NewSelfSubjectReview(kubeCfgFile string, logger log.Logger) authenticator.Request {
34+
return &SelfSubjectReview{
35+
logger: logger,
36+
RemoteKubeConfigFile: kubeCfgFile,
37+
}
38+
}
39+
40+
// AuthenticateRequest implements the authenticator.Request interface.
41+
func (s *SelfSubjectReview) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
42+
authHeader := req.Header.Get("Authorization")
43+
level.Debug(s.logger).Log("msg", "Extracting token from Authorization header", "header", authHeader)
44+
if authHeader == "" {
45+
return nil, false, nil
46+
}
47+
48+
const bearerPrefix = "Bearer "
49+
if !strings.HasPrefix(authHeader, bearerPrefix) {
50+
return nil, false, fmt.Errorf("invalid authorization header format")
51+
}
52+
53+
token := strings.TrimPrefix(authHeader, bearerPrefix)
54+
cfg, err := getConfig(s.RemoteKubeConfigFile)
55+
if err != nil {
56+
return nil, false, err
57+
}
58+
59+
cfg = rest.AnonymousClientConfig(cfg)
60+
cfg.BearerToken = token
61+
clientset, err := kubernetes.NewForConfig(cfg)
62+
if err != nil {
63+
return nil, false, err
64+
}
65+
66+
ssr, err := clientset.AuthenticationV1().SelfSubjectReviews().Create(context.Background(), &authenticationv1.SelfSubjectReview{}, metav1.CreateOptions{})
67+
if err != nil {
68+
return nil, false, err
69+
}
70+
71+
level.Debug(s.logger).Log("msg", "SelfSubjectReview",
72+
"username", ssr.Status.UserInfo.Username,
73+
"uid", ssr.Status.UserInfo.UID,
74+
"groups", fmt.Sprintf("%v", ssr.Status.UserInfo.Groups),
75+
"extra", fmt.Sprintf("%v", ssr.Status.UserInfo.Extra))
76+
77+
extra := make(map[string][]string)
78+
for k, v := range ssr.Status.UserInfo.Extra {
79+
extra[k] = v
80+
}
81+
return &authenticator.Response{
82+
User: &k8suser.DefaultInfo{
83+
Name: ssr.Status.UserInfo.Username,
84+
UID: ssr.Status.UserInfo.UID,
85+
Groups: ssr.Status.UserInfo.Groups,
86+
Extra: extra,
87+
},
88+
}, true, nil
89+
}
90+
91+
func getConfig(kubeconfig string) (*rest.Config, error) {
92+
if len(kubeconfig) > 0 {
93+
loader := &clientcmd.ClientConfigLoadingRules{ExplicitPath: kubeconfig}
94+
return loadConfig(loader)
95+
}
96+
kubeconfigPath := os.Getenv(clientcmd.RecommendedConfigPathEnvVar)
97+
if len(kubeconfigPath) == 0 {
98+
return rest.InClusterConfig() //nolint:wrapcheck
99+
}
100+
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
101+
if _, ok := os.LookupEnv("HOME"); !ok {
102+
u, err := user.Current()
103+
if err != nil {
104+
return nil, fmt.Errorf("could not get current user: %w", err)
105+
}
106+
p := path.Join(u.HomeDir, clientcmd.RecommendedHomeDir, clientcmd.RecommendedFileName)
107+
loadingRules.Precedence = append(loadingRules.Precedence, p)
108+
}
109+
return loadConfig(loadingRules)
110+
}
111+
112+
func loadConfig(loader clientcmd.ClientConfigLoader) (*rest.Config, error) {
113+
return clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loader, nil).ClientConfig() //nolint:wrapcheck
114+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ require (
4848
google.golang.org/genproto/googleapis/api v0.0.0-20241015192408-796eee8c2d53
4949
google.golang.org/grpc v1.68.1
5050
google.golang.org/protobuf v1.35.2
51+
k8s.io/api v0.30.5
5152
k8s.io/apimachinery v0.30.5
5253
k8s.io/apiserver v0.30.5
5354
k8s.io/client-go v0.30.5
@@ -188,7 +189,6 @@ require (
188189
gopkg.in/ini.v1 v1.67.0 // indirect
189190
gopkg.in/yaml.v2 v2.4.0 // indirect
190191
gopkg.in/yaml.v3 v3.0.1 // indirect
191-
k8s.io/api v0.30.5 // indirect
192192
k8s.io/component-base v0.30.5 // indirect
193193
k8s.io/klog/v2 v2.130.1 // indirect
194194
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect

main.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,7 @@ type tenant struct {
230230
ServiceAccount string `json:"serviceAccount"`
231231
RedirectURL string `json:"redirectURL"`
232232
CookieSecret string `json:"cookieSecret"`
233+
SSREnabled bool `json:"ssrEnabled"`
233234
config map[string]interface{}
234235
} `json:"openshift"`
235236
Authenticator *struct {

0 commit comments

Comments
 (0)