Skip to content

Commit

Permalink
[v1.7 release] merge v1.7 to main (#439)
Browse files Browse the repository at this point in the history
  • Loading branch information
mamy-CS authored Jun 10, 2024
2 parents fde401b + f7973f3 commit 8cb2177
Show file tree
Hide file tree
Showing 47 changed files with 838 additions and 518 deletions.
16 changes: 16 additions & 0 deletions .github/compute-version/action.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
name: Compute Version
description: computes version of tornjak
outputs:
version:
description: "tornjak version"
value: ${{ steps.version.outputs.version }}
runs:
using: composite
steps:
- name: Generate Version
id: version
shell: bash
run: |
version="$(cat version.txt | cut -d '.' -f -2)"
echo VERSION=$version
echo "version=$version" >> $GITHUB_OUTPUT
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:
EOF
- name: Check out repository code
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.6

- name: Install Golang
uses: actions/setup-go@v5.0.0
uses: actions/setup-go@v5.0.1
with:
go-version-file: go.mod
check-latest: true
Expand All @@ -39,7 +39,7 @@ jobs:
run: go mod download

- name: golangci-lint
uses: golangci/golangci-lint-action@v4.0.0
uses: golangci/golangci-lint-action@v6.0.1
with:
version: v1.57.2
args: --timeout 7m
Expand Down
21 changes: 15 additions & 6 deletions .github/workflows/master-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ jobs:
EOF
- name: Check out repository code
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.6

- name: Log in to GHCR.io
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.2.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
Expand All @@ -30,11 +31,15 @@ jobs:
- name: Get branch name
id: branch_name
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"

- name: Compute Tornjak version
uses: ./.github/compute-version
id: version

- name: Run build
uses: ./.github/actions/build
with:
tag-version: ${{ contains(fromJSON('["main", "v1.6"]'), steps.branch_name.outputs.branch) && true || false }}
tag-version: ${{ contains(fromJSON('["main", "${{ steps.version.outputs.version }}"]'), steps.branch_name.outputs.branch) && true || false }}

- name: Print job result
run: |
Expand All @@ -55,9 +60,9 @@ jobs:
EOF
- name: Check out repository code
uses: actions/checkout@v4.1.2
uses: actions/checkout@v4.1.6
- name: Log in to GHCR.io
uses: docker/login-action@v3.1.0
uses: docker/login-action@v3.2.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
Expand All @@ -67,13 +72,17 @@ jobs:
id: branch_name
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"

- name: Compute Tornjak version
uses: ./.github/compute-version
id: version

- name: Run build
uses: ./.github/actions/build
with:
image-tag-prefix: ubi-
backend-dockerfile: Dockerfile.backend-container.ubi
frontend-dockerfile: frontend/Dockerfile.frontend-container.ubi
tag-version: ${{ contains(fromJSON('["main", "v1.6"]'), steps.branch_name.outputs.branch) && true || false }}
tag-version: ${{ contains(fromJSON('["main", "${{ steps.version.outputs.version }}"]'), steps.branch_name.outputs.branch) && true || false }}

- name: Print job result
run: |
Expand Down
164 changes: 117 additions & 47 deletions api/agent/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ import (
"github.com/hashicorp/hcl/hcl/token"
"github.com/pkg/errors"

auth "github.com/spiffe/tornjak/pkg/agent/auth"
agentdb "github.com/spiffe/tornjak/pkg/agent/db"
"github.com/spiffe/tornjak/pkg/agent/authentication/authenticator"
"github.com/spiffe/tornjak/pkg/agent/authorization"
)

type Server struct {
Expand All @@ -36,7 +37,8 @@ type Server struct {

// Plugins
Db agentdb.AgentDB
Auth auth.Auth
Authenticator authenticator.Authenticator
Authorizer authorization.Authorizer
}

// config type, as defined by SPIRE
Expand Down Expand Up @@ -429,15 +431,18 @@ func (s *Server) verificationMiddleware(next http.Handler) http.Handler {
cors(w, r)
return
}
err := s.Auth.Verify(r)

userInfo := s.Authenticator.AuthenticateRequest(r)

err := s.Authorizer.AuthorizeRequest(r, userInfo)
if err != nil {
emsg := fmt.Sprintf("Error authorizing request: %v", err.Error())
// error should be written already
retError(w, emsg, http.StatusUnauthorized)
return
} else {
next.ServeHTTP(w, r)
}

next.ServeHTTP(w, r)
}
return http.HandlerFunc(f)
}
Expand Down Expand Up @@ -540,41 +545,61 @@ func (s *Server) home(w http.ResponseWriter, r *http.Request) {
}
}

func (s *Server) health(w http.ResponseWriter, r *http.Request) {
var ret = "Endpoint is healthy."

cors(w, r)
je := json.NewEncoder(w)

var err = je.Encode(ret)
if err != nil {
emsg := fmt.Sprintf("Error: %v", err.Error())
retError(w, emsg, http.StatusBadRequest)
}
}


func (s *Server) GetRouter() http.Handler {
rtr := mux.NewRouter()

apiRtr := rtr.PathPrefix("/").Subrouter()
healthRtr := rtr.PathPrefix("/healthz").Subrouter()

// Healthcheck (never goes through authn/authz layers)
healthRtr.HandleFunc("", s.health)

// Home
rtr.HandleFunc("/", s.home)
apiRtr.HandleFunc("/", s.home)

// SPIRE server healthcheck
rtr.HandleFunc("/api/debugserver", s.debugServer)
rtr.HandleFunc("/api/healthcheck", s.healthcheck)
apiRtr.HandleFunc("/api/debugserver", s.debugServer)
apiRtr.HandleFunc("/api/healthcheck", s.healthcheck)

// Agents
rtr.HandleFunc("/api/agent/list", s.agentList)
rtr.HandleFunc("/api/agent/ban", s.agentBan)
rtr.HandleFunc("/api/agent/delete", s.agentDelete)
rtr.HandleFunc("/api/agent/createjointoken", s.agentCreateJoinToken)
apiRtr.HandleFunc("/api/agent/list", s.agentList)
apiRtr.HandleFunc("/api/agent/ban", s.agentBan)
apiRtr.HandleFunc("/api/agent/delete", s.agentDelete)
apiRtr.HandleFunc("/api/agent/createjointoken", s.agentCreateJoinToken)

// Entries
rtr.HandleFunc("/api/entry/list", s.entryList)
rtr.HandleFunc("/api/entry/create", s.entryCreate)
rtr.HandleFunc("/api/entry/delete", s.entryDelete)
apiRtr.HandleFunc("/api/entry/list", s.entryList)
apiRtr.HandleFunc("/api/entry/create", s.entryCreate)
apiRtr.HandleFunc("/api/entry/delete", s.entryDelete)

// Tornjak specific
rtr.HandleFunc("/api/tornjak/serverinfo", s.tornjakGetServerInfo)
apiRtr.HandleFunc("/api/tornjak/serverinfo", s.tornjakGetServerInfo)
// Agents Selectors
rtr.HandleFunc("/api/tornjak/selectors/register", s.tornjakPluginDefine)
rtr.HandleFunc("/api/tornjak/selectors/list", s.tornjakSelectorsList)
rtr.HandleFunc("/api/tornjak/agents/list", s.tornjakAgentsList)
apiRtr.HandleFunc("/api/tornjak/selectors/register", s.tornjakPluginDefine)
apiRtr.HandleFunc("/api/tornjak/selectors/list", s.tornjakSelectorsList)
apiRtr.HandleFunc("/api/tornjak/agents/list", s.tornjakAgentsList)
// Clusters
rtr.HandleFunc("/api/tornjak/clusters/list", s.clusterList)
rtr.HandleFunc("/api/tornjak/clusters/create", s.clusterCreate)
rtr.HandleFunc("/api/tornjak/clusters/edit", s.clusterEdit)
rtr.HandleFunc("/api/tornjak/clusters/delete", s.clusterDelete)
apiRtr.HandleFunc("/api/tornjak/clusters/list", s.clusterList)
apiRtr.HandleFunc("/api/tornjak/clusters/create", s.clusterCreate)
apiRtr.HandleFunc("/api/tornjak/clusters/edit", s.clusterEdit)
apiRtr.HandleFunc("/api/tornjak/clusters/delete", s.clusterDelete)

// Middleware
rtr.Use(s.verificationMiddleware)
apiRtr.Use(s.verificationMiddleware)

// UI
spa := spaHandler{staticPath: "ui-agent", indexPath: "index.html"}
Expand Down Expand Up @@ -755,40 +780,78 @@ func NewAgentsDB(dbPlugin *ast.ObjectItem) (agentdb.AgentDB, error) {
}
}

// NewAuth returns a new Auth
func NewAuth(authPlugin *ast.ObjectItem) (auth.Auth, error) {
key, data, _ := getPluginConfig(authPlugin)
/*if err != nil { // default used, no error
verifier := auth.NewNullVerifier()
return verifier, nil
}*/
// NewAuthenticator returns a new Authenticator
func NewAuthenticator(authenticatorPlugin *ast.ObjectItem) (authenticator.Authenticator, error) {
key, data, _ := getPluginConfig(authenticatorPlugin)

switch key {
case "KeycloakAuth":
case "Keycloak":
// check if data is defined
if data == nil {
return nil, errors.New("KeycloakAuth UserManagement plugin ('config > plugins > UserManagement KeycloakAuth > plugin_data') no populated")
return nil, errors.New("Keycloak Authenticator plugin ('config > plugins > Authenticator Keycloak > plugin_data') not populated")
}
fmt.Printf("KeycloakAuth Usermanagement Data: %+v\n", data)
fmt.Printf("Authenticator Keycloak Plugin Data: %+v\n", data)
// decode config to struct
var config pluginAuthKeycloak
var config pluginAuthenticatorKeycloak
if err := hcl.DecodeObject(&config, data); err != nil {
return nil, errors.Errorf("Couldn't parse Auth config: %v", err)
return nil, errors.Errorf("Couldn't parse Authenticator config: %v", err)
}

// Log warning if audience is nil that aud claim is not checked
if config.Audience == "" {
fmt.Printf("WARNING: Auth plugin has no expected audience configured - `aud` claim will not be checked (please populate 'config > plugins > UserManagement KeycloakAuth > plugin_data > audience')")
fmt.Println("WARNING: Auth plugin has no expected audience configured - `aud` claim will not be checked (please populate 'config > plugins > UserManagement KeycloakAuth > plugin_data > audience')")
}

// create authenticator TODO make json an option?
authenticator, err := authenticator.NewKeycloakAuthenticator(true, config.IssuerURL, config.Audience)
if err != nil {
return nil, errors.Errorf("Couldn't configure Authenticator: %v", err)
}
return authenticator, nil
default:
return nil, errors.Errorf("Invalid option for Authenticator named %s", key)
}
}

// NewAuthorizer returns a new Authorizer
func NewAuthorizer(authorizerPlugin *ast.ObjectItem) (authorization.Authorizer, error) {
key, data, _ := getPluginConfig(authorizerPlugin)

switch key {
case "RBAC":
// check if data is defined
if data == nil {
return nil, errors.New("RBAC Authorizer plugin ('config > plugins > Authorizer RBAC > plugin_data') not populated")
}
fmt.Printf("Authorizer RBAC Plugin Data: %+v\n", data)

// create verifier TODO make json an option?
verifier, err := auth.NewKeycloakVerifier(true, config.IssuerURL, config.Audience)
// decode config to struct
var config pluginAuthorizerRBAC
if err := hcl.DecodeObject(&config, data); err != nil {
return nil, errors.Errorf("Couldn't parse Authorizer config: %v", err)
}

// decode into role list and apiMapping
roleList := make(map[string]string)
apiMapping := make(map[string][]string)
for _, role := range config.RoleList {
roleList[role.Name] = role.Desc
// print warning for empty string
if role.Name == "" {
fmt.Println("WARNING: using the empty string for an API enables access to all authenticated users")
}
}
for _, api := range config.APIRoleMappings {
apiMapping[api.Name] = api.AllowedRoles
}

authorizer, err := authorization.NewRBACAuthorizer(config.Name, roleList, apiMapping)
if err != nil {
return nil, errors.Errorf("Couldn't configure Auth: %v", err)
return nil, errors.Errorf("Couldn't configure Authorizer: %v", err)
}
return verifier, nil
return authorizer, nil
default:
return nil, errors.Errorf("Invalid option for UserManagement named %s", key)
return nil, errors.Errorf("Invalid option for Authorizer named %s", key)
}
}

Expand All @@ -814,7 +877,8 @@ func (s *Server) VerifyConfiguration() error {

func (s *Server) ConfigureDefaults() error {
// no authorization is a default
s.Auth = auth.NewNullVerifier()
s.Authenticator = authenticator.NewNullAuthenticator()
s.Authorizer = authorization.NewNullAuthorizer()
return nil
}

Expand Down Expand Up @@ -864,11 +928,17 @@ func (s *Server) Configure() error {
if err != nil {
return errors.Errorf("Cannot configure datastore plugin: %v", err)
}
// configure auth
case "UserManagement":
s.Auth, err = NewAuth(pluginObject)
// configure Authenticator
case "Authenticator":
s.Authenticator, err = NewAuthenticator(pluginObject)
if err != nil {
return errors.Errorf("Cannot configure Authenticator plugin: %v", err)
}
// configure Authorizer
case "Authorizer":
s.Authorizer, err = NewAuthorizer(pluginObject)
if err != nil {
return errors.Errorf("Cannot configure auth plugin: %v", err)
return errors.Errorf("Cannot configure Authorizer plugin: %v", err)
}
}
// TODO Handle when multiple plugins configured
Expand Down
18 changes: 17 additions & 1 deletion api/agent/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,23 @@ type pluginDataStoreSQL struct {
Filename string `hcl:"filename"`
}

type pluginAuthKeycloak struct {
type pluginAuthenticatorKeycloak struct {
IssuerURL string `hcl:"issuer"`
Audience string `hcl:"audience"`
}

type AuthRole struct {
Name string `hcl:",key"`
Desc string `hcl:"desc"`
}

type APIRoleMapping struct {
Name string `hcl:",key"`
AllowedRoles []string `hcl:"allowed_roles"`
}

type pluginAuthorizerRBAC struct {
Name string `hcl:"name"`
RoleList []*AuthRole `hcl:"role,block"`
APIRoleMappings []*APIRoleMapping `hcl:"API,block"`
}
Binary file added docs/auth/keycloak/diagrams/browser-flow.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/auth/keycloak/diagrams/github-keycloak.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/auth/keycloak/diagrams/github-oauth-app.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/auth/keycloak/diagrams/google-keycloak.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/auth/keycloak/diagrams/microsoft-azure.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
Loading

0 comments on commit 8cb2177

Please sign in to comment.