Fix oc-auth for k8s integration

This commit is contained in:
plm
2025-01-21 15:23:45 +01:00
parent 27e2df2310
commit 776aac5d43
5 changed files with 247 additions and 16 deletions

View File

@@ -1,6 +1,8 @@
package auth_connectors
import (
"bytes"
"context"
"encoding/base64"
"encoding/json"
"errors"
@@ -10,6 +12,7 @@ import (
"net/url"
"oc-auth/conf"
"oc-auth/infrastructure/claims"
"os"
"regexp"
"strconv"
"strings"
@@ -18,6 +21,10 @@ import (
oclib "cloud.o-forge.io/core/oc-lib"
"cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/tools"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)
type HydraConnector struct {
@@ -102,12 +109,26 @@ func (a HydraConnector) Refresh(token *Token) (*Token, error) {
}
func (a HydraConnector) tryLog(username string, url string, subpath string, challenge string, cookies ...*http.Cookie) (*Redirect, string, []*http.Cookie, error) {
resp, err := a.Caller.CallRaw(http.MethodGet, url, subpath,
map[string]interface{}{}, "application/json", true, cookies...)
if err != nil || resp.Request.Response == nil || resp.Request.Response.Header["Set-Cookie"] == nil {
postBody, _ := json.Marshal(map[string]interface{}{})
responseBody := bytes.NewBuffer(postBody)
req, _ := http.NewRequest(http.MethodGet, url+subpath, responseBody)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("X-Forwarded-Proto", "https")
for _, c := range cookies {
req.AddCookie(c)
}
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // No redirect, doesn't make sense; hydra redirect user to login page, we are not the user here due to wrong oauth flow implementation
},
}
resp, err := client.Do(req)
if err != nil || resp == nil || resp.Header["Set-Cookie"] == nil {
return nil, "", cookies, err
}
cc := resp.Request.Response.Header["Set-Cookie"] // retrieve oauth2 csrf token cookie
cc := resp.Header["Set-Cookie"] // retrieve oauth2 csrf token cookie
if len(cc) > 0 {
for _, c := range cc {
first := strings.Split(c, ";")
@@ -117,7 +138,7 @@ func (a HydraConnector) tryLog(username string, url string, subpath string, chal
})
}
}
return a.challenge(username, resp.Request.URL.String(), challenge, cookies...)
return a.challenge(username, resp.Header.Get("Location"), challenge, cookies...)
}
func (a HydraConnector) getClient() string {
@@ -146,8 +167,22 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
return nil, err
}
// problem with consent THERE we need to accept the consent challenge && get the token
_, err = a.Caller.CallRaw(http.MethodGet, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), "", map[string]interface{}{},
"application/json", true, cookies...)
postBody, _ := json.Marshal(map[string]interface{}{})
responseBody := bytes.NewBuffer(postBody)
req, _ := http.NewRequest(http.MethodGet, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), responseBody)
req.Header.Set("Content-Type", "application/json")
req.Header.Add("X-Forwarded-Proto", "https")
for _, c := range cookies {
req.AddCookie(c)
}
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse // No redirect, doesn't make sense; hydra redirect user to login page, we are not the user here due to wrong oauth flow implementation
},
}
_, err = client.Do(req)
if err != nil {
s := strings.Split(err.Error(), "\"")
if len(s) > 1 && strings.Contains(s[1], "access_token") {
@@ -160,6 +195,15 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
Username: username,
}
urls := url.Values{}
// Using k8s secrets gen by hydra, eventually
clientID, clientSecret, err := a.getOAuth2Conf(conf.GetConfig().OAuth2ClientSecretNamespace, conf.GetConfig().OAuth2ClientSecretName)
if err == nil {
urls.Add("client_id", clientID)
urls.Add("client_secret", clientSecret)
}
// Fallback on manually set client secret
urls.Add("client_id", clientID)
urls.Add("client_secret", conf.GetConfig().ClientSecret)
urls.Add("grant_type", "client_credentials")
@@ -194,6 +238,54 @@ func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Toke
return token, nil
}
func (a HydraConnector) getOAuth2Conf(namespace string, secretName string) (string, string, error) {
clientset, err := a.getClientset()
if err != nil {
return "", "", fmt.Errorf("error creating Kubernetes client: %v", err)
}
secret, err := clientset.CoreV1().Secrets(namespace).Get(context.TODO(), secretName, metav1.GetOptions{})
if err != nil {
return "", "", fmt.Errorf("error retrieving secret %s/%s: %v", namespace, secretName, err)
}
clientIDEncoded, found := secret.Data["CLIENT_ID"]
if !found {
return "", "", fmt.Errorf("CLIENT_ID key not found in secret")
}
clientSecretEncoded, found := secret.Data["CLIENT_SECRET"]
if !found {
return "", "", fmt.Errorf("CLIENT_SECRET key not found in secret")
}
clientID := string(clientIDEncoded)
clientSecret := string(clientSecretEncoded)
return clientID, clientSecret, nil
}
func (a HydraConnector) getClientset() (*kubernetes.Clientset, error) {
var config *rest.Config
var err error
// Check if running inside cluster
if _, inCluster := os.LookupEnv("KUBERNETES_SERVICE_HOST"); inCluster {
config, err = rest.InClusterConfig() // Use in-cluster config
} else {
kubeconfig := os.Getenv("KUBECONFIG") // Use local kubeconfig file
if kubeconfig == "" {
kubeconfig = clientcmd.RecommendedHomeFile
}
config, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
}
if err != nil {
return nil, err
}
return kubernetes.NewForConfig(config)
}
func (a HydraConnector) Logout(token string, cookies ...*http.Cookie) (*Token, error) {
access := strings.Split(token, ".")
if len(access) > 2 {
@@ -242,9 +334,10 @@ func (a HydraConnector) Introspect(token string, cookie ...*http.Cookie) (bool,
}
func (a HydraConnector) getPath(isAdmin bool, isOauth bool) string {
host := conf.GetConfig().AuthConnectorHost
host := conf.GetConfig().AuthConnectPublicHost
port := fmt.Sprintf("%v", conf.GetConfig().AuthConnectorPort)
if isAdmin {
host = conf.GetConfig().AuthConnectorHost
port = fmt.Sprintf("%v", conf.GetConfig().AuthConnectorAdminPort) + "/admin"
}
oauth := ""

View File

@@ -342,7 +342,7 @@ func (k KetoConnector) createRelationShip(object string, relation string, subjec
}
host := conf.GetConfig().PermissionConnectorHost
port := fmt.Sprintf("%v", conf.GetConfig().PermissionConnectorAdminPort)
b, err := caller.CallPut("http://"+host+":"+port, "/relation-tuples", body)
b, err := caller.CallPut("http://"+host+":"+port, "/admin/relation-tuples", body)
if err != nil {
log := oclib.GetLogger()
log.Error().Msg(err.Error())