This commit is contained in:
mr 2024-10-30 12:38:25 +01:00
parent 7198c40d30
commit d87883b57f
27 changed files with 536 additions and 755 deletions

View File

@ -1,5 +1,8 @@
FROM golang:alpine as builder
ARG HOSTNAME=http://localhost
ARG NAME=local
WORKDIR /app
COPY . .

Binary file not shown.

View File

@ -3,7 +3,8 @@ package conf
import "sync"
type Config struct {
PVKPath string
PublicKeyPath string
PrivateKeyPath string
LDAPEndpoints string
LDAPBindDN string

View File

@ -1,18 +1,16 @@
package controllers
import (
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"oc-auth/infrastructure"
auth_connectors "oc-auth/infrastructure/auth_connector"
"oc-auth/infrastructure/claims"
"regexp"
"strings"
oclib "cloud.o-forge.io/core/oc-lib"
model "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/static"
beego "github.com/beego/beego/v2/server/web"
)
@ -78,22 +76,6 @@ func (o *OAuthController) LoginLDAP() {
o.ServeJSON()
}
// @Title Claims
// @Description enrich token with claims
// @Param body body models.Token true "The token info"
// @Success 200 {string}
// @router /claims [post]
func (o *OAuthController) Claims() {
// enrich token with claims
var res claims.Claims
json.Unmarshal(o.Ctx.Input.CopyBody(100000), &res)
claims := res.Session.IDToken["id_token_claims"].(map[string]interface{})
userName := claims["sub"].(string)
_, loc := static.GetMyLocalJsonPeer()
o.Data["json"] = infrastructure.GetClaims().AddClaimsToToken(userName, loc["url"].(string))
o.ServeJSON()
}
// @Title Introspection
// @Description introspect token
// @Param body body models.Token true "The token info"
@ -126,6 +108,7 @@ func (o *OAuthController) Introspect() {
} else {
reqToken = splitToken[1]
}
token, err := infrastructure.GetAuthConnector().Introspect(reqToken)
if err != nil || !token {
o.Data["json"] = err
@ -134,37 +117,55 @@ func (o *OAuthController) Introspect() {
o.ServeJSON()
}
var whitelist = []string{
"/login",
"/refresh",
"/introspect",
}
// @Title AuthForward
// @Description auth forward
// @Param Authorization header string false "auth token"
// @Param body body models.workflow true "The workflow content"
// @Success 200 {string}
// @router /forward [get]
func (o *OAuthController) InternalAuthForward() {
fmt.Println("InternalAuthForward")
reqToken := o.Ctx.Request.Header.Get("Authorization")
if reqToken == "" {
for _, w := range whitelist {
if strings.Contains(o.Ctx.Request.Header.Get("X-Forwarded-Uri"), w) {
o.Ctx.ResponseWriter.WriteHeader(200)
o.ServeJSON()
return
}
}
o.Ctx.ResponseWriter.WriteHeader(401)
o.ServeJSON()
return
}
splitToken := strings.Split(reqToken, "Bearer ")
if len(splitToken) < 2 {
reqToken = ""
} else {
reqToken = splitToken[1]
}
origin, publicKey, external := o.extractOrigin()
if reqToken != "" && !o.checkAuthForward(reqToken, publicKey) && origin != "" {
fmt.Println("Unauthorized", origin, reqToken)
origin, publicKey, _ := o.extractOrigin()
if !infrastructure.GetAuthConnector().CheckAuthForward( //reqToken != "" &&
reqToken, publicKey, origin,
o.Ctx.Request.Header.Get("X-Forwarded-Method"),
o.Ctx.Request.Header.Get("X-Forwarded-Uri")) && origin != "" && publicKey != "" {
o.Ctx.ResponseWriter.WriteHeader(401)
o.ServeJSON()
return
}
token, err := infrastructure.GetAuthConnector().Introspect(reqToken, &http.Cookie{
isToken, err := infrastructure.GetAuthConnector().Introspect(reqToken, &http.Cookie{
Name: "csrf_token",
Value: o.XSRFToken(),
}) // may be a problem... we should check if token is valid on our side
// prefers a refresh token call
if err != nil || external {
fmt.Println("Unauthorized 2", err, external) // error
fmt.Println("InternalAuthForward", isToken, err)
if err != nil || !isToken {
o.Ctx.ResponseWriter.WriteHeader(401)
} else if token && !external { // redirect to login
o.Data["json"] = token
}
o.ServeJSON()
}
@ -176,50 +177,25 @@ func (o *OAuthController) extractOrigin() (string, string, bool) {
if origin == "" {
origin = o.Ctx.Request.Header.Get("Origin")
}
idLoc, loc := static.GetMyLocalJsonPeer()
searchStr := origin
r := regexp.MustCompile("(:[0-9]+)")
t := r.FindString(searchStr)
if t != "" {
searchStr = strings.Replace(searchStr, t, "", -1)
}
peer := oclib.Search(nil, searchStr, oclib.LibDataEnum(oclib.PEER))
if peer.Code != 200 || len(peer.Data) == 0 { // TODO: add state of partnership
return "", "", external
}
p := peer.Data[0].(*model.Peer)
publicKey = p.PublicKey
origin = p.Url
if origin != "" { // is external
peer := oclib.Search(nil, origin, oclib.LibDataEnum(oclib.PEER))
if peer.Code != 200 {
return "", "", external
}
p := peer.Data[0]
if strings.Contains(origin, "localhost") || strings.Contains(origin, "127.0.0.1") || idLoc == p.GetID() {
if strings.Contains(origin, "localhost") || strings.Contains(origin, "127.0.0.1") || p.State == model.SELF {
external = false
}
publicKey = p.(*model.Peer).PublicKey
} else {
external = false
publicKey = loc["public_key"].(string)
}
return origin, publicKey, external
}
func (o *OAuthController) checkAuthForward(reqToken string, publicKey string) bool {
bytes, err := base64.StdEncoding.DecodeString(reqToken) // Converting data
if err != nil {
fmt.Println("Failed to Decode secret", err)
return false
}
var decodedToken map[string]interface{}
err = json.Unmarshal(bytes, &decodedToken)
if err != nil {
fmt.Println("Failed to parse secret", err)
return false
} else if decodedToken["session"] != nil {
host := o.Ctx.Request.Header.Get("X-Forwarded-Host")
method := o.Ctx.Request.Header.Get("X-Forwarded-Method")
forward := o.Ctx.Request.Header.Get("X-Forwarded-Uri")
if forward == "" || method == "" {
fmt.Println("Forwarded headers are missing")
return false
}
// ask keto for permission is in claims
ok, err := infrastructure.GetClaims().DecodeClaimsInToken(
host, method, forward, decodedToken["session"].(map[string]interface{}), publicKey)
if err != nil {
fmt.Println("Failed to decode claims", err)
}
return ok
}
return false
}

View File

@ -1,13 +1,45 @@
version: '3.4'
services:
traefik:
image: traefik:v2.10.4
container_name: traefik
networks:
- catalog
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--log.level=DEBUG"
ports:
- "8080:80"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
whoami: # TEST PURPOSE
image: traefik/whoami
container_name: whoami
networks:
- catalog
ports:
- "5000:80"
labels:
- "traefik.enable=true"
- "traefik.http.routers.obg.entrypoints=web"
- "traefik.http.routers.obg.rule=Host(`localhost`)"
- "traefik.http.routers.obg.tls=false"
- "traefik.http.services.obg.loadbalancer.server.port=80"
- "traefik.http.routers.obg.middlewares=oc-auth"
oc-auth:
image: 'oc-auth:latest'
ports:
- 8094:8080
container_name: oc-auth
labels:
- "traefik.enable=true"
- "traefik.http.middlewares.auth.forwardauth.address=http://oc-auth:8080/oc/forward"
- "traefik.http.middlewares.auth.forwardauth.authResponseHeaders=X-Forwarded-User"
- "traefik.http.services.auth.loadbalancer.server.port=8080"
environment:
PVK_PATH: /etc/oc/pvk.pem
LDAP_ENDPOINTS: ldap:389
LDAP_BINDDN: cn=admin,dc=example,dc=com
LDAP_BINDPW: password
@ -15,6 +47,8 @@ services:
LDAP_ROLE_BASEDN: "ou=AppRoles,dc=example,dc=com"
networks:
- catalog
volumes:
- ./pem:/etc/oc/pem
networks:
catalog:
external: true

View File

@ -3,5 +3,7 @@
"MONGO_DATABASE":"DC_myDC",
"NATS_URL": "nats://nats:4222",
"PORT" : 8080,
"AUTH_CONNECTOR_HOST": "hydra"
"AUTH_CONNECTOR_HOST": "hydra",
"PRIVATE_KEY_PATH": "/etc/oc/pem/private.pem",
"PUBLIC_KEY_PATH": "/etc/oc/pem/public.pem"
}

5
go.mod
View File

@ -3,11 +3,12 @@ module oc-auth
go 1.22.0
require (
cloud.o-forge.io/core/oc-lib v0.0.0-20241025125832-c34e5579fcfc
cloud.o-forge.io/core/oc-lib v0.0.0-20241030105814-5f05b73366ab
github.com/beego/beego/v2 v2.3.1
github.com/nats-io/nats.go v1.37.0
github.com/ory/hydra-client-go v1.11.8
github.com/smartystreets/goconvey v1.7.2
go.uber.org/zap v1.27.0
golang.org/x/oauth2 v0.23.0
)
@ -69,7 +70,6 @@ require (
go.opentelemetry.io/proto/otlp v1.0.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/mod v0.17.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
@ -113,6 +113,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/purnaresa/bulwark v0.0.0-20201001150757-1cec324746b2
github.com/robfig/cron/v3 v3.0.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/shiena/ansicolor v0.0.0-20230509054315-a9deabde6e02 // indirect

8
go.sum
View File

@ -67,6 +67,12 @@ cloud.o-forge.io/core/oc-lib v0.0.0-20241023092234-cc8fc2df2161 h1:T4Lkp3H/I/dZb
cloud.o-forge.io/core/oc-lib v0.0.0-20241023092234-cc8fc2df2161/go.mod h1:t+zpCTVKVdHH/BImwtMYY2QIWLMXKgY4n/JhFm3Vpu8=
cloud.o-forge.io/core/oc-lib v0.0.0-20241025125832-c34e5579fcfc h1:kDoodLH2HuIuZ0oppb3z+9YBYl7XwneXlqSGlYh/BnY=
cloud.o-forge.io/core/oc-lib v0.0.0-20241025125832-c34e5579fcfc/go.mod h1:t+zpCTVKVdHH/BImwtMYY2QIWLMXKgY4n/JhFm3Vpu8=
cloud.o-forge.io/core/oc-lib v0.0.0-20241030091613-1a5521237800 h1:uZ4Qrxk/KEpOfDq8QHjZankW7aZGLlDYLoM3CZowlR8=
cloud.o-forge.io/core/oc-lib v0.0.0-20241030091613-1a5521237800/go.mod h1:t+zpCTVKVdHH/BImwtMYY2QIWLMXKgY4n/JhFm3Vpu8=
cloud.o-forge.io/core/oc-lib v0.0.0-20241030101941-20ce1f5ef37b h1:jFjjZ5sfHM6G17/TphmEhkC0crg75+33tunEmQ3Z6KI=
cloud.o-forge.io/core/oc-lib v0.0.0-20241030101941-20ce1f5ef37b/go.mod h1:t+zpCTVKVdHH/BImwtMYY2QIWLMXKgY4n/JhFm3Vpu8=
cloud.o-forge.io/core/oc-lib v0.0.0-20241030105814-5f05b73366ab h1:hYUf9xXpqhp9w0eBfOWVi7c17iWpN+FL2FbhsAkmQ2E=
cloud.o-forge.io/core/oc-lib v0.0.0-20241030105814-5f05b73366ab/go.mod h1:t+zpCTVKVdHH/BImwtMYY2QIWLMXKgY4n/JhFm3Vpu8=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8=
github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
@ -451,6 +457,8 @@ github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/purnaresa/bulwark v0.0.0-20201001150757-1cec324746b2 h1:5w7Y/+01L0ErOp3qFiiAEpVlvzYJK2mjbLFcbBkx2OI=
github.com/purnaresa/bulwark v0.0.0-20201001150757-1cec324746b2/go.mod h1:/fUyI4rS5nHkKtgxNRU/uuFyhx9woSy3wKQSCQjqWN4=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=

12
index.html Normal file
View File

@ -0,0 +1,12 @@
Hostname: a3ff4096def1
IP: 127.0.0.1
IP: 172.18.0.16
RemoteAddr: 172.18.0.1:46314
GET / HTTP/1.1
Host: localhost:5000
User-Agent: Wget/1.20.3 (linux-gnu)
Accept: */*
Accept-Encoding: identity
Connection: Keep-Alive
X-Auth-Token: eaaafd18-0fed-4b3a-81b4-663c99ec1cbb

View File

@ -12,15 +12,17 @@ type AuthConnector interface {
Logout(token string, cookies ...*http.Cookie) (*Token, error)
Introspect(token string, cookie ...*http.Cookie) (bool, error)
Refresh(token *Token) (*Token, error)
CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string) bool
}
type Token struct {
Active bool `json:"active"`
AccessToken string `json:"access_token"`
ExpiresIn int `json:"expires_in"`
Challenge string `json:"challenge"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
TokenType string `json:"token_type"`
Username string `json:"username,omitempty"`
Password string `json:"password,omitempty"`
}
type Redirect struct {

View File

@ -1,6 +1,7 @@
package auth_connectors
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
@ -8,10 +9,12 @@ import (
"net/http"
"net/url"
"oc-auth/conf"
"oc-auth/infrastructure/claims"
"regexp"
"strconv"
"strings"
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"
)
@ -81,10 +84,18 @@ func (a HydraConnector) challenge(username string, url string, challenge string,
}
func (a HydraConnector) Refresh(token *Token) (*Token, error) {
access := strings.Split(token.AccessToken, ".")
if len(access) > 2 {
token.AccessToken = strings.Join(access[0:2], ".")
}
isValid, err := a.Introspect(token.AccessToken)
if err != nil || !isValid {
return nil, err
}
_, err = a.Logout(token.AccessToken)
if err != nil {
return nil, err
}
return a.Login(token.Username)
}
@ -107,32 +118,6 @@ func (a HydraConnector) tryLog(username string, url string, subpath string, chal
return a.challenge(username, resp.Request.URL.String(), challenge, cookies...)
}
func (a HydraConnector) extractTokenFromFragment(fragment string) *Token {
splittedFragment := strings.Split(fragment, "#")
f := splittedFragment[0]
if len(splittedFragment) > 1 {
f = splittedFragment[1]
}
token := &Token{}
frags := strings.Split(f, "&")
for _, f := range frags {
splittedFrag := strings.Split(f, "=")
if len(splittedFrag) > 1 {
if splittedFrag[0] == "access_token" {
token.AccessToken = strings.ReplaceAll(splittedFrag[1], "ory_at_", "")
}
if splittedFrag[0] == "expires_in" {
i, err := strconv.Atoi(splittedFrag[1])
if err != nil {
return nil
}
token.ExpiresIn = i
}
}
}
return token
}
func (a HydraConnector) getClient() string {
resp, err := a.Caller.CallGet(a.getPath(true, false), "/clients")
if err != nil {
@ -148,44 +133,63 @@ func (a HydraConnector) getClient() string {
func (a HydraConnector) Login(username string, cookies ...*http.Cookie) (t *Token, err error) {
clientID := a.getClient()
redirect, challenge, cookies, err := a.tryLog(username, a.getPath(false, true),
redirect, _, cookies, err := a.tryLog(username, a.getPath(false, true),
"/auth?client_id="+clientID+"&response_type="+strings.ReplaceAll(a.ResponseType, " ", "%20")+"&scope="+strings.ReplaceAll(a.Scopes, " ", "%20")+"&state="+a.State,
"login", cookies...)
if err != nil || redirect == nil {
return nil, err
}
redirect, challenge, cookies, err = a.tryLog(username, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), "", "consent", cookies...)
redirect, _, cookies, err = a.tryLog(username, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), "", "consent", cookies...)
if err != nil || redirect == nil {
return nil, err
}
// problem with consent THERE we need to accept the consent challenge && get the token
url := ""
resp, err := a.Caller.CallRaw(http.MethodGet, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), "", map[string]interface{}{},
_, err = a.Caller.CallRaw(http.MethodGet, a.urlFormat(redirect.RedirectTo, a.getPath(false, true)), "", map[string]interface{}{},
"application/json", true, cookies...)
fmt.Println(err)
if err != nil {
s := strings.Split(err.Error(), "\"")
if len(s) > 1 && strings.Contains(s[1], "access_token") {
url = s[1]
err = nil
} else {
return nil, err
}
} else {
url = resp.Request.Response.Request.URL.Fragment
}
token := a.extractTokenFromFragment(url)
fmt.Println(url, token)
if token == nil || token.AccessToken == "" {
return nil, errors.New("no token found")
token := &Token{
Username: username,
}
token.Challenge = challenge
token.Active = true
token.Username = username
fmt.Println(token)
urls := url.Values{}
urls.Add("client_id", clientID)
urls.Add("client_secret", conf.GetConfig().ClientSecret)
urls.Add("grant_type", "client_credentials")
resp, err := a.Caller.CallForm(http.MethodPost, a.getPath(false, true), "/token", urls,
"application/x-www-form-urlencoded", true, cookies...)
var m map[string]interface{}
defer resp.Body.Close()
b, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
err = json.Unmarshal(b, &token)
if err != nil {
return nil, err
}
json.Unmarshal(b, &m)
pp := oclib.Search(nil, string(peer.SELF.EnumIndex()), oclib.LibDataEnum(oclib.PEER))
if len(pp.Data) == 0 || pp.Code >= 300 || pp.Err != "" {
return nil, errors.New("peer not found")
}
c := claims.GetClaims().AddClaimsToToken(username, pp.Data[0].(*peer.Peer).Url)
b, _ = json.Marshal(c)
token.AccessToken = strings.ReplaceAll(token.AccessToken, "ory_at_", "") + "." + base64.StdEncoding.EncodeToString(b)
return token, nil
}
func (a HydraConnector) Logout(token string, cookies ...*http.Cookie) (*Token, error) {
access := strings.Split(token, ".")
if len(access) > 2 {
token = strings.Join(access[0:2], ".")
}
p := a.getPath(false, true) + "/revoke"
urls := url.Values{}
urls.Add("token", token)
@ -203,6 +207,10 @@ func (a HydraConnector) Logout(token string, cookies ...*http.Cookie) (*Token, e
func (a HydraConnector) Introspect(token string, cookie ...*http.Cookie) (bool, error) {
// check validity of the token by calling introspect endpoint
// if token is not active, we need to re-authenticate by sending the user to the login page
access := strings.Split(token, ".")
if len(access) > 2 {
token = strings.Join(access[0:2], ".")
}
urls := url.Values{}
urls.Add("token", token)
resp, err := a.Caller.CallForm(http.MethodPost, a.getPath(true, true), "/introspect", urls,
@ -238,3 +246,29 @@ func (a HydraConnector) getPath(isAdmin bool, isOauth bool) string {
return "http://" + host + ":" + port + oauth
}
func (a HydraConnector) CheckAuthForward(reqToken string, publicKey string, host string, method string, forward string) bool {
fmt.Println("CheckAuthForward", reqToken, publicKey, host, method, forward)
if forward == "" || method == "" {
fmt.Println("Forwarded headers are missing")
return false
}
var c claims.Claims
token := strings.Split(reqToken, ".")
if len(token) > 2 {
bytes, err := base64.StdEncoding.DecodeString(token[2])
if err != nil {
return false
}
err = json.Unmarshal(bytes, &c)
if err != nil {
return false
}
}
// ask keto for permission is in claims
ok, err := claims.GetClaims().DecodeClaimsInToken(host, method, forward, c, publicKey)
if err != nil {
fmt.Println("Failed to decode claims", err)
}
return ok
}

View File

@ -1,9 +1,11 @@
package claims
import "oc-auth/conf"
// Tokenizer interface
type ClaimService interface {
AddClaimsToToken(userId string, host string) Claims
DecodeClaimsInToken(host string, method string, forward string, sessionClaims map[string]interface{}, publicKey string) (bool, error)
DecodeClaimsInToken(host string, method string, forward string, sessionClaims Claims, publicKey string) (bool, error)
}
// SessionClaims struct
@ -16,3 +18,11 @@ type SessionClaims struct {
type Claims struct {
Session SessionClaims `json:"session"`
}
var t = map[string]ClaimService{
"hydra": HydraClaims{},
}
func GetClaims() ClaimService {
return t[conf.GetConfig().Auth]
}

View File

@ -0,0 +1,129 @@
package claims
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"errors"
"log"
)
func SignDefault(plaintext, privateKey []byte) (signature string, err error) {
client, err := New(privateKey, nil)
if err != nil {
log.Println(err)
return
}
signatureByte, err := client.Sign(plaintext)
if err != nil {
log.Println(err)
return
}
signature = base64.StdEncoding.EncodeToString(signatureByte)
return
}
func VerifyDefault(plaintext, publicKey []byte, signature string) (err error) {
publicKeys := make(map[string][]byte)
publicKeys["default"] = publicKey
client, err := New(nil, publicKeys)
if err != nil {
log.Println(err)
return
}
signatureByte, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
log.Println(err)
return
}
err = client.Verify(plaintext, signatureByte, "default")
if err != nil {
log.Println(err)
return
}
return
}
func (c *Client) Sign(plaintext []byte) (signature []byte, err error) {
var opts rsa.PSSOptions
opts.SaltLength = rsa.PSSSaltLengthAuto
newhash := crypto.SHA256
pssh := newhash.New()
pssh.Write(plaintext)
hashed := pssh.Sum(nil)
signature, err = rsa.SignPSS(
rand.Reader,
c.PrivateKey,
newhash,
hashed,
&opts,
)
return
}
func (c *Client) Verify(plaintext, signature []byte, target string) (err error) {
var opts rsa.PSSOptions
opts.SaltLength = rsa.PSSSaltLengthAuto
newhash := crypto.SHA256
pssh := newhash.New()
pssh.Write(plaintext)
hashed := pssh.Sum(nil)
err = rsa.VerifyPSS(
c.PublicKeys[target],
newhash,
hashed,
signature,
&opts,
)
return
}
func init() {
log.SetFlags(log.LstdFlags | log.Lshortfile)
}
type Client struct {
PrivateKey *rsa.PrivateKey
PublicKeys map[string]*rsa.PublicKey
}
func New(privateKey []byte, publicKeys map[string][]byte) (client *Client, err error) {
client = &Client{}
if privateKey != nil {
validPrivateKey, errPrivate := x509.ParsePKCS1PrivateKey(privateKey)
if errPrivate != nil {
err = errPrivate
log.Println(err)
return
}
client.PrivateKey = validPrivateKey
}
if publicKeys != nil {
validPublicKeysMap := make(map[string]*rsa.PublicKey)
for k, v := range publicKeys {
validPublicKey, errPublic := x509.ParsePKCS1PublicKey(v)
if errPublic != nil {
err = errPublic
log.Println(err)
return
}
if validPublicKey == nil {
err = errors.New("Invalid Public Key Type")
log.Println(err)
return
}
validPublicKeysMap[k] = validPublicKey
}
client.PublicKeys = validPublicKeysMap
}
return
}

View File

@ -1,13 +1,10 @@
package claims
import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"oc-auth/conf"
"oc-auth/infrastructure/perms_connectors"
"oc-auth/infrastructure/utils"
@ -43,11 +40,10 @@ func (h HydraClaims) decodeKey(key string) (tools.METHOD, string, error) {
}
func (h HydraClaims) DecodeSignature(host string, signature string, publicKey string) (bool, error) {
fmt.Println("DecodeSignature", host)
hashed := sha256.Sum256([]byte(host))
// get public key into a variable
spkiBlock, _ := pem.Decode([]byte(publicKey))
key, _ := x509.ParsePKCS1PublicKey(spkiBlock.Bytes)
err := rsa.VerifyPKCS1v15(key, crypto.SHA256, hashed[:], []byte(signature))
spkiBlock, _ := pem.Decode([]byte(publicKey)) // get public key into a variable
err := VerifyDefault(hashed[:], spkiBlock.Bytes, signature)
if err != nil {
return false, err
}
@ -55,32 +51,28 @@ func (h HydraClaims) DecodeSignature(host string, signature string, publicKey st
}
func (h HydraClaims) encodeSignature(host string) (string, error) {
fmt.Println("encodeSignature", host)
hashed := sha256.Sum256([]byte(host))
// READ FILE TO GET PRIVATE KEY FROM PVK PEM PATH
content, err := os.ReadFile(conf.GetConfig().PVKPath)
content, err := os.ReadFile(conf.GetConfig().PrivateKeyPath)
if err != nil {
return "", err
}
privateKey := string(content)
spkiBlock, _ := pem.Decode([]byte(privateKey))
key, _ := x509.ParsePKCS1PrivateKey(spkiBlock.Bytes)
signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hashed[:])
if err != nil {
return "", err
}
return string(signature), nil
return SignDefault(hashed[:], spkiBlock.Bytes)
}
func (h HydraClaims) DecodeClaimsInToken(host string, method string, forward string, sessionClaims map[string]interface{}, publicKey string) (bool, error) {
if sessionClaims["id_token"] == nil || sessionClaims["access_token"] == nil {
return false, errors.New("invalid session claims")
func (h HydraClaims) DecodeClaimsInToken(host string, method string, forward string, sessionClaims Claims, publicKey string) (bool, error) {
idTokenClaims := sessionClaims.Session.IDToken
if idTokenClaims["signature"] == nil {
return false, errors.New("no signature found")
}
idTokenClaims := sessionClaims["id_token"].(map[string]interface{})
signature := idTokenClaims["signature"].(string)
if ok, err := h.DecodeSignature(host, signature, publicKey); !ok {
return false, err
}
claims := sessionClaims["access_token"].(map[string]interface{})
claims := sessionClaims.Session.AccessToken
path := strings.ReplaceAll(forward, "http://"+host, "")
splittedPath := strings.Split(path, "/")
for m, p := range claims {
@ -114,6 +106,8 @@ func (h HydraClaims) AddClaimsToToken(userId string, host string) Claims {
if err != nil {
return claims
}
claims.Session.AccessToken = make(map[string]interface{})
claims.Session.IDToken = make(map[string]interface{})
for _, perm := range perms {
key, err := h.generateKey(perm.Relation, perm.Object)
if err != nil {
@ -127,7 +121,6 @@ func (h HydraClaims) AddClaimsToToken(userId string, host string) Claims {
}
claims.Session.IDToken["signature"] = sign
return claims
}
// add signature in the token MISSING

View File

@ -9,10 +9,6 @@ import (
"cloud.o-forge.io/core/oc-lib/tools"
)
var t = map[string]claims.ClaimService{
"hydra": claims.HydraClaims{},
}
var a = map[string]auth_connectors.AuthConnector{
"hydra": auth_connectors.HydraConnector{
Caller: tools.NewHTTPCaller(map[tools.DataType]map[tools.METHOD]string{}),
@ -28,5 +24,5 @@ func GetPermissionConnector() perms_connectors.PermConnector {
}
func GetClaims() claims.ClaimService {
return t[conf.GetConfig().Auth]
return claims.GetClaims()
}

View File

@ -1,466 +0,0 @@
# Ory Hydra Configuration
#
#
# !!WARNING!!
# This configuration file is for documentation purposes only. Do not use it in production. As all configuration items
# are enabled, it will not work out of the box either.
#
#
# Ory Hydra can be configured using a configuration file and passing the file location using `--config path/to/config.yaml`.
# Per default, Ory Hydra will look up and load file ~/.hydra.yaml. All configuration keys can be set using environment
# variables as well.
#
# Setting environment variables is easy:
#
## Linux / OSX
#
# $ export MY_ENV_VAR=foo
# $ hydra ...
#
# alternatively:
#
# $ MY_ENV_VAR=foo hydra ...
#
## Windows
#
### Command Prompt
#
# > set MY_ENV_VAR=foo
# > hydra ...
#
### Powershell
#
# > $env:MY_ENV_VAR="foo"
# > hydra ...
#
## Docker
#
# $ docker run -e MY_ENV_VAR=foo oryd/hydra:...
#
#
# Assuming the following configuration layout:
#
# serve:
# public:
# port: 4444
# something_else: foobar
#
# Key `something_else` can be set as an environment variable by uppercasing it's path:
# `serve.public.port.somethihng_else` -> `SERVE.PUBLIC.PORT.SOMETHING_ELSE`
# and replacing `.` with `_`:
# `serve.public.port.somethihng_else` -> `SERVE_PUBLIC_PORT_SOMETHING_ELSE`
#
# Environment variables always override values from the configuration file. Here are some more examples:
#
# Configuration key | Environment variable |
# ------------------|----------------------|
# dsn | DSN |
# serve.admin.host | SERVE_ADMIN_HOST |
# ------------------|----------------------|
#
#
# List items such as
#
#secrets:
# system:
# - oc-auth-got-secret
# - this-is-an-old-secret
# - this-is-another-old-secret
#
# must be separated using `,` when using environment variables. The environment variable equivalent to the code section#
# above is:
#
# Linux/macOS: $ export SECRETS_SYSTEM=this-is-the-primary-secret,this-is-an-old-secret,this-is-another-old-secret
# Windows: > set SECRETS_SYSTEM=this-is-the-primary-secret,this-is-an-old-secret,this-is-another-old-secret
# log configures the logger
log:
# Sets the log level, supports "panic", "fatal", "error", "warn", "info" and "debug". Defaults to "info".
level: info
# Sets the log format. Leave it undefined for text based log format, or set to "json" for JSON formatting.
format: json
# serve controls the configuration for the http(s) daemon(s).
serve:
# public controls the public daemon serving public API endpoints like /oauth2/auth, /oauth2/token, /.well-known/jwks.json
public:
# The port to listen on. Defaults to 4444
port: 4444
# The interface or unix socket Ory Hydra should listen and handle public API requests on.
# Use the prefix "unix:" to specify a path to a unix socket.
# Leave empty to listen on all interfaces.
host: localhost # leave this out or empty to listen on all devices which is the default
# host: unix:/path/to/socket
# socket:
# owner: hydra
# group: hydra
# mode: 0775
# cors configures Cross Origin Resource Sharing for public endpoints.
cors:
# set enabled to true to enable CORS. Defaults to false.
enabled: true
# allowed_origins is a list of origins (comma separated values) a cross-domain request can be executed from.
# If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*)
# to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.
#
# If empty or undefined, this defaults to `*`, allowing CORS from every domain (if cors.enabled: true).
allowed_origins:
- https://example.com
- https://*.example.com
# allowed_methods is list of HTTP methods the user agent is allowed to use with cross-domain
# requests. Defaults to the methods listed.
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
# A list of non simple headers the client is allowed to use with cross-domain requests. Defaults to the listed values.
allowed_headers:
- Authorization
- Content-Type
# Sets which headers (comma separated values) are safe to expose to the API of a CORS API specification. Defaults to the listed values.
exposed_headers:
- Content-Type
# Sets whether the request can include user credentials like cookies, HTTP authentication
# or client side SSL certificates. Defaults to true.
allow_credentials: true
# Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request
# is preceded by a preflight request. Defaults to 0.
max_age: 10
# If set to true, adds additional log output to debug server side CORS issues. Defaults to false.
debug: true
# Access Log configuration for public server.
request_log:
# Disable access log for health and metrics endpoints.
disable_for_health: false
# admin controls the admin daemon serving admin API endpoints like /jwk, /client, ...
admin:
# The port to listen on. Defaults to 4445
port: 4445
# The interface or unix socket Ory Hydra should listen and handle administrative API requests on.
# Use the prefix "unix:" to specify a path to a unix socket.
# Leave empty to listen on all interfaces.
host: localhost # leave this out or empty to listen on all devices which is the default
# host: unix:/path/to/socket
# socket:
# owner: hydra
# group: hydra
# mode: 0775
# cors configures Cross Origin Resource Sharing for admin endpoints.
cors:
# set enabled to true to enable CORS. Defaults to false.
enabled: true
# allowed_origins is a list of origins (comma separated values) a cross-domain request can be executed from.
# If the special * value is present in the list, all origins will be allowed. An origin may contain a wildcard (*)
# to replace 0 or more characters (i.e.: http://*.domain.com). Only one wildcard can be used per origin.
#
# If empty or undefined, this defaults to `*`, allowing CORS from every domain (if cors.enabled: true).
allowed_origins:
- https://example.com
- https://*.example.com
# allowed_methods is list of HTTP methods the user agent is allowed to use with cross-domain
# requests. Defaults to GET and POST.
allowed_methods:
- POST
- GET
- PUT
- PATCH
- DELETE
# A list of non simple headers the client is allowed to use with cross-domain requests. Defaults to the listed values.
allowed_headers:
- Authorization
- Content-Type
# Sets which headers (comma separated values) are safe to expose to the API of a CORS API specification. Defaults to the listed values.
exposed_headers:
- Content-Type
# Sets whether the request can include user credentials like cookies, HTTP authentication
# or client side SSL certificates.
allow_credentials: true
# Sets how long (in seconds) the results of a preflight request can be cached. If set to 0, every request
# is preceded by a preflight request. Defaults to 0.
max_age: 10
# If set to true, adds additional log output to debug server side CORS issues. Defaults to false.
debug: true
# Access Log configuration for admin server.
request_log:
# Disable access log for health endpoints.
disable_for_health: false
# tls configures HTTPS (HTTP over TLS). If configured, the server automatically supports HTTP/2.
tls:
# key configures the private key (pem encoded)
key:
# The key can either be loaded from a file:
path: /path/to/key.pem
# Or from a base64 encoded (without padding) string:
base64: LS0tLS1CRUdJTiBFTkNSWVBURUQgUFJJVkFURSBLRVktLS0tLVxuTUlJRkRqQkFCZ2txaGtpRzl3MEJCUTB3...
# cert configures the TLS certificate (PEM encoded)
cert:
# The cert can either be loaded from a file:
path: /path/to/cert.pem
# Or from a base64 encoded (without padding) string:
base64: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tXG5NSUlEWlRDQ0FrMmdBd0lCQWdJRVY1eE90REFOQmdr...
# Whitelist one or multiple CIDR address ranges and allow them to terminate TLS connections.
# Be aware that the X-Forwarded-Proto header must be set and must never be modifiable by anyone but
# your proxy / gateway / load balancer. Supports ipv4 and ipv6.
#
# Hydra serves http instead of https when this option is set.
#
# For more information head over to: https://www.ory.sh/docs/hydra/production#tls-termination
allow_termination_from:
- 127.0.0.1/32
cookies:
# specify the SameSite mode that cookies should be sent with
same_site_mode: Lax
# Some older browser versions don't work with SameSite=None. This option enables the workaround
# defined in https://web.dev/samesite-cookie-recipes/ which essentially stores a second cookie
# without SameSite as a fallback.
same_site_legacy_workaround: false
# dsn sets the data source name. This configures the backend where Ory Hydra persists data.
#
## In-memory database
#
# If dsn is "memory", data will be written to memory and is lost when you restart this instance.
# You can set this value using the DSN environment variable:
#
## SQL databases
#
# Ory Hydra supports popular SQL databases. For more detailed configuration information go to:
# https://www.ory.sh/docs/hydra/dependencies-environment#sql
#
### PostgreSQL (recommended)
#
# If dsn is starting with postgres:// PostgreSQL will be used as storage backend:
# dsn: dsn=postgres://user:password@host:123/database
#
### MySQL database
#
# If dsn is starting with mysql:// MySQL will be used as storage backend:
# dsn: mysql://user:password@tcp(host:123)/database
#
### CockroachDB
#
# If dsn is starting with cockroach:// CockroachDB will be used as storage backend:
# dsn: cockroach://user:password@host:123/database
#
dsn: memory
# dsn: postgres://user:password@host:123/database
# dsn: mysql://user:password@tcp(host:123)/database
# hsm configures Hardware Security Module for hydra.openid.id-token, hydra.jwt.access-token keys
# Either slot or token_label must be set. If token_label is set, then first slot in index with this label is used.
hsm:
enabled: false
library: /path/to/hsm-vendor/library.so
pin: token-pin-code
slot: 0
token_label: hydra
# Key set prefix can be used in case of multiple Ory Hydra instances need to store keys on the same HSM partition.
# For example if `hsm.key_set_prefix=app1.` then key set `hydra.openid.id-token` would be generated/requested/deleted
# on HSM with `CKA_LABEL=app1.hydra.openid.id-token`.
key_set_prefix: app1.
# webfinger configures ./well-known/ settings
webfinger:
# jwks configures the /.well-known/jwks.json endpoint.
jwks:
# broadcast_keys is a list of JSON Web Keys that should be exposed at that endpoint. This is usually
# the public key for verifying OpenID Connect ID Tokens. However, you might want to add additional keys here as well.
broadcast_keys:
- hydra.openid.id-token # This key is always exposed by default
# - hydra.jwt.access-token # This key will be exposed when the OAuth2 Access Token strategy is set to JWT.
# oidc_discovery configures OpenID Connect Discovery (/.well-known/openid-configuration)
oidc_discovery:
client_registration_url: https://my-service.com/clients
# A list of supported claims to be broadcasted. Claim `sub` is always included:
supported_claims:
- email
- username
# The scope OAuth 2.0 Clients may request. Scope `offline`, `offline_access`, and `openid` are always included.
supported_scope:
- email
- whatever
- read.photos
# A URL of the userinfo endpoint to be advertised at the OpenID Connect
# Discovery endpoint /.well-known/openid-configuration. Defaults to Ory Hydra's userinfo endpoint at /userinfo.
# Set this value if you want to handle this endpoint yourself.
userinfo_url: https://example.org/my-custom-userinfo-endpoint
# oidc configures OpenID Connect features.
oidc:
# subject_identifiers configures the Subject Identifier algorithm.
#
# For more information please head over to the documentation:
# -> https://www.ory.sh/docs/hydra/advanced#subject-identifier-algorithms
subject_identifiers:
# which algorithms to enable. Defaults to "public"
supported_types:
- pairwise
- public
# configures the pairwise algorithm
pairwise:
# if "pairwise" is enabled, the salt must be defined.
salt: some-random-salt
# dynamic_client_registration configures OpenID Connect Dynamic Client Registration (exposed as admin endpoints /clients/...)
dynamic_client_registration:
enabled: false
# The OpenID Connect Dynamic Client Registration specification has no concept of whitelisting OAuth 2.0 Scope. If you
# want to expose Dynamic Client Registration, you should set the default scope enabled for newly registered clients.
# Keep in mind that users can overwrite this default by setting the "scope" key in the registration payload,
# effectively disabling the concept of whitelisted scopes.
default_scope:
- openid
- offline
- offline_access
urls:
self:
# This value will be used as the "issuer" in access and ID tokens. It must be
# specified and using HTTPS protocol, unless --dev is set. This should typically be equal
# to the public value.
issuer: https://localhost:4444/
# This is the base location of the public endpoints of your Ory Hydra installation. This should typically be equal
# to the issuer value. If left unspecified, it falls back to the issuer value.
public: https://localhost:4444/
# Sets the login endpoint of the User Login & Consent flow. Defaults to an internal fallback URL.
login: https://my-login.app/login
# Sets the consent endpoint of the User Login & Consent flow. Defaults to an internal fallback URL.
consent: https://my-consent.app/consent
# Sets the logout endpoint. Defaults to an internal fallback URL.
logout: https://my-logout.app/logout
# Sets the error endpoint. The error ui will be shown when an OAuth2 error occurs that which can not be sent back
# to the client. Defaults to an internal fallback URL.
error: https://my-error.app/error
# When a user agent requests to logout, it will be redirected to this url afterwards per default.
post_logout_redirect: https://my-example.app/logout-successful
strategies:
scope: DEPRECATED_HIERARCHICAL_SCOPE_STRATEGY
# You may use JSON Web Tokens as access tokens.
#
# But seriously. Don't do that. It's not a great idea and has a ton of caveats and subtle security implications. Read more:
# -> https://www.ory.sh/docs/hydra/advanced#json-web-tokens
#
# access_token: jwt
# configures time to live
ttl:
# configures how long a user login and consent flow may take. Defaults to 1h.
login_consent_request: 1h
# configures how long access tokens are valid. Defaults to 1h.
access_token: 1h
# configures how long refresh tokens are valid. Defaults to 720h. Set to -1 for refresh tokens to never expire.
refresh_token: 720h
# configures how long id tokens are valid. Defaults to 1h.
id_token: 1h
# configures how long auth codes are valid. Defaults to 10m.
auth_code: 10m
oauth2:
# Set this to true if you want to share error debugging information with your OAuth 2.0 clients.
# Keep in mind that debug information is very valuable when dealing with errors, but might also expose database error
# codes and similar errors. Defaults to false.
expose_internal_errors: true
# Configures hashing algorithms. Supports only BCrypt at the moment.
hashers:
# Configures the BCrypt hashing algorithm used for hashing Client Secrets.
bcrypt:
# Sets the BCrypt cost. Minimum value is 4 and default value is 10. The higher the value, the more CPU time is being
# used to generate hashes.
cost: 10
pkce:
# Set this to true if you want PKCE to be enforced for all clients.
enforced: false
# Set this to true if you want PKCE to be enforced for public clients.
enforced_for_public_clients: false
session:
# store encrypted data in database, default true
encrypt_at_rest: true
## refresh_token_rotation
# By default Refresh Tokens are rotated and invalidated with each use. See https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.13.2 for more details
refresh_token_rotation:
#
## grace_period
#
# Set the grace period for refresh tokens to be reused. Such reused tokens will result in multiple refresh tokens being issued.
#
# Examples:
# - 5s
# - 1m
grace_period: 0s
# The secrets section configures secrets used for encryption and signing of several systems. All secrets can be rotated,
# for more information on this topic navigate to:
# -> https://www.ory.sh/docs/hydra/advanced#rotation-of-hmac-token-signing-and-database-and-cookie-encryption-keys
secrets:
# The system secret must be at least 16 characters long. If none is provided, one will be generated. They key
# is used to encrypt sensitive data using AES-GCM (256 bit) and validate HMAC signatures.
#
# The first item in the list is used for signing and encryption. The whole list is used for verifying signatures
# and decryption.
system:
- this-is-the-primary-secret
- this-is-an-old-secret
- this-is-another-old-secret
# A secret that is used to encrypt cookie sessions. Defaults to secrets.system. It is recommended to use
# a separate secret in production.
#
# The first item in the list is used for signing and encryption. The whole list is used for verifying signatures
# and decryption.
cookie:
- this-is-the-primary-secret
- this-is-an-old-secret
- this-is-another-old-secret
# Enables profiling if set. Use "cpu" to enable cpu profiling and "mem" to enable memory profiling. For more details
# on profiling, head over to: https://blog.golang.org/profiling-go-programs
profiling: cpu
# profiling: mem
# Ory Hydra supports distributed tracing.
tracing:
# Set this to the tracing backend you wish to use. Currently supports jaeger. If omitted or empty, tracing will
# be disabled.
provider: jaeger
# Specifies the service name to use on the tracer.
service_name: Ory Hydra
providers:
# Configures the jaeger tracing backend.
jaeger:
# The address of the jaeger-agent where spans should be sent to
local_agent_address: 127.0.0.1:6831
sampling:
# The value passed to the sampler type that has been configured.
# Supported values: This is dependant on the sampling strategy used:
# - const: 0 or 1 (all or nothing)
# - rateLimiting: a constant rate (e.g. setting this to 3 will sample requests with the rate of 3 traces per second)
# - probabilistic: a value between 0..1
trace_id_ratio: 1.0
# The address of jaeger-agent's HTTP sampling server
server_url: http://localhost:5778/sampling

View File

@ -1,7 +1,7 @@
version: "3"
services:
services:
hydra-client:
image: hydra-home # oryd/hydra:v2.2.0
image: oryd/hydra:v2.2.0
container_name: hydra-client
environment:
HYDRA_ADMIN_URL: http://hydra:4445
@ -9,8 +9,6 @@ services:
command:
- create
- oauth2-client
- --skip-consent
- --skip-logout-consent
- --skip-tls-verify
- --name
- test-client
@ -19,7 +17,7 @@ services:
- --response-type
- id_token,token,code
- --grant-type
- implicit,refresh_token,authorization_code
- implicit,refresh_token,authorization_code,client_credentials
- --scope
- openid,profile,email,roles
- --token-endpoint-auth-method
@ -42,10 +40,11 @@ services:
retries: 10
hydra:
container_name: hydra
image: hydra-home # oryd/hydra:v2.2.0
image: oryd/hydra:v2.2.0
environment:
SECRETS_SYSTEM: oc-auth-got-secret
LOG_LEAK_SENSITIVE_VALUES: true
# OAUTH2_TOKEN_HOOK_URL: http://oc-auth:8080/oc/claims
URLS_SELF_ISSUER: http://hydra:4444
URLS_SELF_PUBLIC: http://hydra:4444
WEBFINGER_OIDC_DISCOVERY_SUPPORTED_SCOPES: profile,email,phone,roles
@ -64,7 +63,7 @@ services:
ldap:
image: pgarrett/ldap-alpine
container_name: ldap
volumes:
volumes:
- "./ldap.ldif:/ldif/ldap.ldif"
networks:
- hydra-net
@ -74,7 +73,7 @@ services:
deploy:
restart_policy:
condition: on-failure
networks:
networks:
hydra-net:
catalog:
external: true

61
main.go
View File

@ -1,12 +1,18 @@
package main
import (
"errors"
"fmt"
"oc-auth/conf"
"oc-auth/infrastructure"
_ "oc-auth/routers"
"os"
"strconv"
"strings"
oclib "cloud.o-forge.io/core/oc-lib"
peer "cloud.o-forge.io/core/oc-lib/models/peer"
"cloud.o-forge.io/core/oc-lib/models/utils"
"cloud.o-forge.io/core/oc-lib/tools"
beego "github.com/beego/beego/v2/server/web"
)
@ -23,7 +29,8 @@ func main() {
// Load the right config file
o := oclib.GetConfLoader()
conf.GetConfig().PVKPath = o.GetStringDefault("PVK_PATH", "./pvk.pem")
conf.GetConfig().PublicKeyPath = o.GetStringDefault("PUBLIC_KEY_PATH", "./pem/public.pem")
conf.GetConfig().PrivateKeyPath = o.GetStringDefault("PRIVATE_KEY_PATH", "./pem/private.pem")
conf.GetConfig().ClientSecret = o.GetStringDefault("CLIENT_SECRET", "oc-auth-got-secret")
conf.GetConfig().Auth = o.GetStringDefault("AUTH", "hydra")
@ -40,11 +47,57 @@ func main() {
conf.GetConfig().LDAPBindPW = o.GetStringDefault("LDAP_BINDPW", "password")
conf.GetConfig().LDAPBaseDN = o.GetStringDefault("LDAP_BASEDN", "dc=example,dc=com")
conf.GetConfig().LDAPRoleBaseDN = o.GetStringDefault("LDAP_ROLE_BASEDN", "ou=AppRoles,dc=example,dc=com")
Discovery()
err := generateSelfPeer()
if err != nil {
panic(err)
}
discovery()
beego.Run()
}
func Discovery() {
func generateSelfPeer() error {
// TODO check if files at private & public path are set
// check if files at private & public path are set
if _, err := os.Stat(conf.GetConfig().PrivateKeyPath); errors.Is(err, os.ErrNotExist) {
return errors.New("private key path does not exist")
}
if _, err := os.Stat(conf.GetConfig().PublicKeyPath); errors.Is(err, os.ErrNotExist) {
return errors.New("public key path does not exist")
}
// check if peer already exists
p := oclib.Search(nil, strconv.Itoa(peer.SELF.EnumIndex()), oclib.LibDataEnum(oclib.PEER))
if len(p.Data) > 0 {
// check public key with the one in the database
f, err := os.ReadFile(conf.GetConfig().PublicKeyPath)
if err != nil {
return err
}
// compare the public key from file with the one in the database
fmt.Println(string(f), p.Data[0].(*peer.Peer).PublicKey)
if !strings.Contains(string(f), p.Data[0].(*peer.Peer).PublicKey) {
return errors.New("public key is different from the one in the database")
}
return nil
}
fmt.Println("Creating new peer", strconv.Itoa(peer.SELF.EnumIndex()))
// create a new peer
o := oclib.GetConfLoader()
peer := &peer.Peer{
Url: o.GetStringDefault("HOSTNAME", "http://localhost"),
AbstractObject: utils.AbstractObject{
Name: o.GetStringDefault("NAME", "local"),
},
PublicKey: conf.GetConfig().PublicKeyPath,
State: peer.SELF,
}
data := oclib.StoreOne(oclib.LibDataEnum(oclib.PEER), peer.Serialize())
if data.Err != "" {
return errors.New(data.Err)
}
return nil
}
func discovery() {
fmt.Println("Discovered")
api := tools.API{}
addPermissions := func(m map[string]interface{}) {

BIN
oc-auth

Binary file not shown.

51
pem/private.pem Normal file
View File

@ -0,0 +1,51 @@
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----

13
pem/public.pem Normal file
View File

@ -0,0 +1,13 @@
-----BEGIN RSA PUBLIC KEY-----
MIICCgKCAgEAw2pdG6wMtuLcP0+k1LFvIb0DQo/oHW2uNJaEJK74plXqp4ztz2dR
b+RQHFLeLuqk4i/zc3b4K3fKPXSlwnVPJCwzPrnyT8jYGOZVlWlETiV9xeJhu6s/
Bh6g1PWz75XjjwV50iv/CEiLNBT23f/3J44wrQzygqNQCiQSALdxWLAEl4l5kHSa
9oMyV70/Uql94/ayMARZsHgp9ZvqQKbkZPw6yzVMfCBxQozlNlo315OHevudhnhp
DRjN5I7zWmqYt6rbXJJC7Y3Izdvzn7QI88RqjSRST5I/7Kz3ndCqrOnI+OQUE5NT
REyQebphvQfTDTKlRPXkdyktdK2DH28Zj6ZF3yjQvN35Q4zhOzlq77dO5IhhopI7
ct8dZH1T1nYkvdyCA/EVMtQsASmBOitH0Y0ACoXQK5Kb6nm/TcM/9ZSJUNiEMuy5
gBZ3YKE9oa4cpTpPXwcA+S/cU7HPNnQAsvD3iJi8GTW9uJs84pn4/WhpQqmXd4rv
hKWECCN3fHy01fUs/U0PaSj2jDY/kQVeXoikNMzPUjdZd9m816TIBh3v3aVXCH/0
iTHHAxctvDgMRb2fpvRJ/wwnYjFG9RpamVFDMvC9NffuYzWAA9IRIY4cqgerfHrV
Z2HHiPTDDvDAIsvImXZc/h7mXN6m3RCQ4Qywy993wd9gUdgg/qnynHcCAwEAAQ==
-----END RSA PUBLIC KEY-----

View File

@ -1 +0,0 @@
{"namespace":"open-cloud","object":"test-role","relation":"-","subject_id":"oc-auth"}

View File

@ -7,15 +7,6 @@ import (
func init() {
beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"],
beego.ControllerComments{
Method: "Claims",
Router: `/claims`,
AllowHTTPMethods: []string{"post"},
MethodParams: param.Make(),
Filters: nil,
Params: nil})
beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"] = append(beego.GlobalControllerRouter["oc-auth/controllers:OAuthController"],
beego.ControllerComments{
Method: "InternalAuthForward",

View File

@ -15,10 +15,8 @@ import (
func init() {
ns := beego.NewNamespace("/oc",
beego.NSNamespace("/auth",
beego.NSInclude(
&controllers.OAuthController{},
),
beego.NSInclude(
&controllers.OAuthController{},
),
beego.NSNamespace("/role",
beego.NSInclude(

View File

@ -15,35 +15,10 @@
},
"basePath": "/oc",
"paths": {
"/auth/claims": {
"post": {
"tags": [
"auth"
],
"description": "enrich token with claims\n\u003cbr\u003e",
"operationId": "OAuthController.Claims",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The token info",
"required": true,
"schema": {
"$ref": "#/definitions/models.Token"
}
}
],
"responses": {
"200": {
"description": "{string}"
}
}
}
},
"/auth/forward": {
"/forward": {
"get": {
"tags": [
"auth"
"oc-auth/controllersOAuthController"
],
"description": "auth forward\n\u003cbr\u003e",
"operationId": "OAuthController.AuthForward",
@ -53,15 +28,6 @@
"name": "Authorization",
"description": "auth token",
"type": "string"
},
{
"in": "body",
"name": "body",
"description": "The workflow content",
"required": true,
"schema": {
"$ref": "#/definitions/models.workflow"
}
}
],
"responses": {
@ -71,10 +37,10 @@
}
}
},
"/auth/introspect": {
"/introspect": {
"get": {
"tags": [
"auth"
"oc-auth/controllersOAuthController"
],
"description": "introspect token\n\u003cbr\u003e",
"operationId": "OAuthController.Introspection",
@ -93,10 +59,10 @@
}
}
},
"/auth/ldap/login": {
"/ldap/login": {
"post": {
"tags": [
"auth"
"oc-auth/controllersOAuthController"
],
"description": "authenticate user\n\u003cbr\u003e",
"operationId": "OAuthController.Login",
@ -118,10 +84,10 @@
}
}
},
"/auth/ldap/logout": {
"/ldap/logout": {
"delete": {
"tags": [
"auth"
"oc-auth/controllersOAuthController"
],
"description": "unauthenticate user\n\u003cbr\u003e",
"operationId": "OAuthController.Logout",
@ -140,31 +106,6 @@
}
}
},
"/auth/refresh": {
"post": {
"tags": [
"auth"
],
"description": "introspect token\n\u003cbr\u003e",
"operationId": "OAuthController.Introspection",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The token info",
"required": true,
"schema": {
"$ref": "#/definitions/models.Token"
}
}
],
"responses": {
"200": {
"description": "{string}"
}
}
}
},
"/permission/": {
"get": {
"tags": [
@ -334,6 +275,31 @@
}
}
},
"/refresh": {
"post": {
"tags": [
"oc-auth/controllersOAuthController"
],
"description": "introspect token\n\u003cbr\u003e",
"operationId": "OAuthController.Introspection",
"parameters": [
{
"in": "body",
"name": "body",
"description": "The token info",
"required": true,
"schema": {
"$ref": "#/definitions/models.Token"
}
}
],
"responses": {
"200": {
"description": "{string}"
}
}
}
},
"/role/": {
"get": {
"tags": [
@ -549,7 +515,7 @@
},
"tags": [
{
"name": "auth",
"name": "oc-auth/controllersOAuthController",
"description": "Operations about auth\n"
},
{

View File

@ -12,28 +12,10 @@ info:
url: https://www.gnu.org/licenses/agpl-3.0.html
basePath: /oc
paths:
/auth/claims:
post:
tags:
- auth
description: |-
enrich token with claims
<br>
operationId: OAuthController.Claims
parameters:
- in: body
name: body
description: The token info
required: true
schema:
$ref: '#/definitions/models.Token'
responses:
"200":
description: '{string}'
/auth/forward:
/forward:
get:
tags:
- auth
- oc-auth/controllersOAuthController
description: |-
auth forward
<br>
@ -43,19 +25,13 @@ paths:
name: Authorization
description: auth token
type: string
- in: body
name: body
description: The workflow content
required: true
schema:
$ref: '#/definitions/models.workflow'
responses:
"200":
description: '{string}'
/auth/introspect:
/introspect:
get:
tags:
- auth
- oc-auth/controllersOAuthController
description: |-
introspect token
<br>
@ -68,10 +44,10 @@ paths:
responses:
"200":
description: '{string}'
/auth/ldap/login:
/ldap/login:
post:
tags:
- auth
- oc-auth/controllersOAuthController
description: |-
authenticate user
<br>
@ -86,10 +62,10 @@ paths:
responses:
"200":
description: '{string}'
/auth/ldap/logout:
/ldap/logout:
delete:
tags:
- auth
- oc-auth/controllersOAuthController
description: |-
unauthenticate user
<br>
@ -102,24 +78,6 @@ paths:
responses:
"200":
description: '{string}'
/auth/refresh:
post:
tags:
- auth
description: |-
introspect token
<br>
operationId: OAuthController.Introspection
parameters:
- in: body
name: body
description: The token info
required: true
schema:
$ref: '#/definitions/models.Token'
responses:
"200":
description: '{string}'
/permission/:
get:
tags:
@ -246,6 +204,24 @@ paths:
responses:
"200":
description: '{auth} string'
/refresh:
post:
tags:
- oc-auth/controllersOAuthController
description: |-
introspect token
<br>
operationId: OAuthController.Introspection
parameters:
- in: body
name: body
description: The token info
required: true
schema:
$ref: '#/definitions/models.Token'
responses:
"200":
description: '{string}'
/role/:
get:
tags:
@ -407,7 +383,7 @@ definitions:
title: workflow
type: object
tags:
- name: auth
- name: oc-auth/controllersOAuthController
description: |
Operations about auth
- name: role