Skip to content
This repository has been archived by the owner on Mar 2, 2022. It is now read-only.

Commit

Permalink
Initial auth implementation
Browse files Browse the repository at this point in the history
Refactor BubblyContext to BubblyConfig to be more understandable

Co-authored-by: lime008 <lime.b.cool@gmail.com>
  • Loading branch information
jlarfors and lime008 committed Sep 22, 2021
1 parent 85bc7c5 commit 3735819
Show file tree
Hide file tree
Showing 54 changed files with 390 additions and 230 deletions.
9 changes: 3 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ commit := $(shell git rev-list -1 HEAD)
version := $(shell git describe --tags --dirty=-dirty)
pre := github.com/valocode/bubbly



all: build

.PHONY: build
Expand Down Expand Up @@ -40,7 +38,7 @@ display-coverage: test-coverage
test-report:
go test -coverprofile=coverage.txt -covermode=atomic -json ./... > test_report.json

# The integration tests depend on Bubbly Server and its Store (currently Postgres) being accessible.
# The integration tests depend on Bubbly Server and its Store (currently Postgres) being accessible.
# This is what the env variables in the beginning of this Makefile are for.
# The count flag prevents Go from caching test results as they are dependent on the DB content.
test-integration:
Expand All @@ -59,8 +57,7 @@ cleanup:
docker-compose down

# Project is CI-enabled with Github Actions. You can run CI locally
# using act (https://github.com/nektos/act).
# using act (https://github.com/nektos/act).
# There are some caveats, but the following target should work:
act:
act:
act -P ubuntu-latest=golang:latest --env-file act.env -j simple

1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

![bubbly-logo](/docs/static/img/bubbly-blue-wide.png)

> Release Readiness in a Bubble.
Expand Down
6 changes: 3 additions & 3 deletions adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import (
"github.com/open-policy-agent/conftest/parser"
"github.com/open-policy-agent/opa/rego"
"github.com/valocode/bubbly/client"
"github.com/valocode/bubbly/config"
"github.com/valocode/bubbly/ent"
"github.com/valocode/bubbly/env"
"github.com/valocode/bubbly/store/api"
)

Expand Down Expand Up @@ -50,7 +50,7 @@ type (
// RunFromID runs an adapter by the given id.
// This will get the relevant adapter (from filesystem or remotely) and perform
// the necessary rego queries and publish that data to the release.
func RunFromID(bCtx *env.BubblyContext, id string, opts ...func(r *runOptions)) (*AdapterResult, error) {
func RunFromID(bCtx *config.BubblyConfig, id string, opts ...func(r *runOptions)) (*AdapterResult, error) {
//
// Get the adapter module to run
//
Expand Down Expand Up @@ -254,7 +254,7 @@ func ParseAdpaterID(id string) (string, string, error) {
// a path to a local file, the name of an adapter (which could lead to a local
// an adapter file in the bubbly directory), or a name:tag for which the adapter
// is fetched remotely.
func adapterFromID(bCtx *env.BubblyContext, id string) (string, error) {
func adapterFromID(bCtx *config.BubblyConfig, id string) (string, error) {
switch strings.Count(id, ":") {
case 0:
// It could be a local adapter, or remote with a default tag.
Expand Down
172 changes: 172 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package auth

import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/labstack/echo/v4"
"golang.org/x/oauth2"
)

// ContextKey is used to store the auth context value in the request context
type ContextKey string

var contextKey ContextKey = "AUTH_CONTEXT"

// Session is used to store authentication data as a context value
type Session struct {
UserID string `json:"sub"`
Email string `json:"email"`
}

// Config defines the configuration used for the authentication middleware and
// the token verifier
type Config struct {
ProviderURL string
ClientID string
ClientSecret string
RedirectURL string

Scopes []string
Enabled bool
}

// Provider stores the runtime configuration and verifier for the authorizer
// and the middleware
type Provider struct {
oidc *oidc.Provider
oidcConfig *oauth2.Config
Verifier *oidc.IDTokenVerifier
Disabled bool
}

// NewProvider creates a new OIDC provider instance
func NewProvider(ctx context.Context, conf *Config) (*Provider, error) {
// TODO: figure out better way to handle auth disabled
if !conf.Enabled {
return &Provider{Disabled: !conf.Enabled}, nil
}
provider, err := oidc.NewProvider(ctx, conf.ProviderURL)
if err != nil {
return nil, fmt.Errorf("creating OIDC provider: %w", err)
}

cf := &oauth2.Config{
ClientID: conf.ClientID,
ClientSecret: conf.ClientSecret,
RedirectURL: conf.RedirectURL,
Scopes: conf.Scopes,
Endpoint: provider.Endpoint(),
}

return &Provider{
oidc: provider,
oidcConfig: cf,
Verifier: provider.Verifier(&oidc.Config{ClientID: cf.ClientID}),
Disabled: !conf.Enabled,
}, nil
}

// AuthorizeHandler validates the authentication code from the client and
// returns a access token as a response
func (p *Provider) AuthorizeHandler(w http.ResponseWriter, r *http.Request) {
if p.Disabled {
http.Error(w, "server authentication disabled", http.StatusMethodNotAllowed)
return
}
ctx := r.Context()
oauth2Token, err := p.oidcConfig.Exchange(ctx, r.URL.Query().Get("code"))
if err != nil {
http.Error(w, "Failed to exchange token: "+err.Error(), http.StatusUnauthorized)
return
}

rawIDToken, ok := oauth2Token.Extra("access_token").(string)
if !ok {
http.Error(w, "No id_token field in oauth2 token.", http.StatusUnauthorized)
return
}
idToken, err := p.Verifier.Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token: "+err.Error(), http.StatusUnauthorized)
return
}

resp := struct {
OAuth2Token *oauth2.Token
IDTokenClaims *json.RawMessage
}{oauth2Token, new(json.RawMessage)}

if err := idToken.Claims(&resp.IDTokenClaims); err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
data, err := json.Marshal(resp)
if err != nil {
http.Error(w, err.Error(), http.StatusUnauthorized)
return
}
w.Write(data)
}

// EchoAuthorizeHandler returns a wrapped http handler for the echo router
func (p *Provider) EchoAuthorizeHandler() echo.HandlerFunc {
return echo.WrapHandler(http.HandlerFunc(p.AuthorizeHandler))
}

// Middleware reads and verifies the bearer tokens and injects the extracted
// data to the request context
func (p *Provider) Middleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
if p.Disabled {
session := &Session{
UserID: "ANONYMOUS",
Email: "noemail",
}
ctx = context.WithValue(ctx, contextKey, session)
r = r.WithContext(ctx)

h.ServeHTTP(w, r)
return
}

rawIDToken := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer ")
idToken, err := p.Verifier.Verify(ctx, rawIDToken)
if err != nil {
http.Error(w, "Failed to verify ID Token", http.StatusUnauthorized)
return
}

session := &Session{}

if err := idToken.Claims(&session); err != nil {
http.Error(w, "Invalid token claims", http.StatusUnauthorized)
return
}

ctx = context.WithValue(ctx, contextKey, session)
r = r.WithContext(ctx)

h.ServeHTTP(w, r)
})
}

// EchoMiddleware returns an instance of the Middleware wrapped for Echo
func (p *Provider) EchoMiddleware() echo.MiddlewareFunc {
return echo.WrapMiddleware(p.Middleware)
}

// GetSession is a helper function to return the session struct stored in
// the request context
func GetSession(ctx context.Context) *Session {
session, ok := ctx.Value(contextKey).(*Session)
if !ok {
return nil
}
return session
}
50 changes: 25 additions & 25 deletions client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,105 +4,105 @@ import (
"fmt"
"net/http"

"github.com/valocode/bubbly/env"
"github.com/valocode/bubbly/config"
"github.com/valocode/bubbly/store/api"
)

func CreateRelease(bCtx *env.BubblyContext, req *api.ReleaseCreateRequest) error {
func CreateRelease(bCtx *config.BubblyConfig, req *api.ReleaseCreateRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPost), WithPayload(req), WithRequestURL("releases"),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPost), WithPayload(req), WithRequestURL("releases"),
)
}

func GetReleases(bCtx *env.BubblyContext, req *api.ReleaseGetRequest) (*api.ReleaseGetResponse, error) {
func GetReleases(bCtx *config.BubblyConfig, req *api.ReleaseGetRequest) (*api.ReleaseGetResponse, error) {
var r api.ReleaseGetResponse
if err := handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithResponse(&r), WithRequestURL("releases"), WithQueryParamsStruct(req),
); err != nil {
return nil, err
}
return &r, nil
}

func SaveCodeScan(bCtx *env.BubblyContext, req *api.CodeScanRequest) error {
func SaveCodeScan(bCtx *config.BubblyConfig, req *api.CodeScanRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithPayload(req), WithRequestURL("codescans"),
)
}

func SaveTestRun(bCtx *env.BubblyContext, req *api.TestRunRequest) error {
func SaveTestRun(bCtx *config.BubblyConfig, req *api.TestRunRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithPayload(req), WithRequestURL("testruns"),
)
}

func GetAdapters(bCtx *env.BubblyContext, req *api.AdapterGetRequest) (*api.AdapterGetResponse, error) {
func GetAdapters(bCtx *config.BubblyConfig, req *api.AdapterGetRequest) (*api.AdapterGetResponse, error) {
var a api.AdapterGetResponse
if err := handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithRequestURL("adapters"), WithQueryParamsStruct(req), WithResponse(&a),
); err != nil {
return nil, err
}
return &a, nil
}

func SaveAdapter(bCtx *env.BubblyContext, req *api.AdapterSaveRequest) error {
func SaveAdapter(bCtx *config.BubblyConfig, req *api.AdapterSaveRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithPayload(req), WithRequestURL("adapters"),
)
}

func GetPolicies(bCtx *env.BubblyContext, req *api.ReleasePolicyGetRequest) (*api.ReleasePolicyGetResponse, error) {
func GetPolicies(bCtx *config.BubblyConfig, req *api.ReleasePolicyGetRequest) (*api.ReleasePolicyGetResponse, error) {
var r api.ReleasePolicyGetResponse
if err := handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithRequestURL("policies"), WithQueryParamsStruct(req), WithResponse(&r),
); err != nil {
return nil, err
}
return &r, nil
}

func SavePolicy(bCtx *env.BubblyContext, req *api.ReleasePolicySaveRequest) error {
func SavePolicy(bCtx *config.BubblyConfig, req *api.ReleasePolicySaveRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithPayload(req), WithRequestURL("policies"),
)
}

func SetPolicy(bCtx *env.BubblyContext, req *api.ReleasePolicyUpdateRequest) error {
func SetPolicy(bCtx *config.BubblyConfig, req *api.ReleasePolicyUpdateRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPut),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPut),
WithPayload(req), WithRequestURL(fmt.Sprintf("policies/%d", *req.ID)),
)
}

func GetVulnerabilityReviews(bCtx *env.BubblyContext, req *api.VulnerabilityReviewGetRequest) (*api.VulnerabilityReviewGetResponse, error) {
func GetVulnerabilityReviews(bCtx *config.BubblyConfig, req *api.VulnerabilityReviewGetRequest) (*api.VulnerabilityReviewGetResponse, error) {
var r api.VulnerabilityReviewGetResponse
if err := handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithRequestURL("vulnerabilityreviews"), WithQueryParamsStruct(req), WithResponse(&r),
); err != nil {
return nil, err
}
return &r, nil
}

func SaveVulnerabilityReview(bCtx *env.BubblyContext, req *api.VulnerabilityReviewSaveRequest) error {
func SaveVulnerabilityReview(bCtx *config.BubblyConfig, req *api.VulnerabilityReviewSaveRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPost),
WithPayload(req), WithRequestURL("vulnerabilityreviews"),
)
}

func UpdateVulnerabilityReview(bCtx *env.BubblyContext, req *api.VulnerabilityReviewUpdateRequest) error {
func UpdateVulnerabilityReview(bCtx *config.BubblyConfig, req *api.VulnerabilityReviewUpdateRequest) error {
return handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodPut),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodPut),
WithPayload(req), WithRequestURL(fmt.Sprintf("vulnerabilityreviews/%d", *req.ID)),
)
}
6 changes: 3 additions & 3 deletions client/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package client
import (
"net/http"

"github.com/valocode/bubbly/env"
"github.com/valocode/bubbly/config"
"github.com/valocode/bubbly/store/api"
)

func GetEvents(bCtx *env.BubblyContext, req *api.EventGetRequest) (*api.EventGetResponse, error) {
func GetEvents(bCtx *config.BubblyConfig, req *api.EventGetRequest) (*api.EventGetResponse, error) {
var r api.EventGetResponse
if err := handleRequest(
WithBubblyContext(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithBubblyConfig(bCtx), WithAPIV1(true), WithMethod(http.MethodGet),
WithResponse(&r), WithRequestURL("events"), WithQueryParamsStruct(req),
); err != nil {
return nil, err
Expand Down
Loading

0 comments on commit 3735819

Please sign in to comment.