Skip to content

Commit

Permalink
feat: support gcp iap authentication for api calls (#552)
Browse files Browse the repository at this point in the history
* feat: support gcp iap authentication for api calls

BREAKING CHANGE: casbin policy requires adjustment

The casbin policy needs to change the request to support an additional provider field in requests.

* test: add provider in api tests

* test: add api auth provider to integration test
  • Loading branch information
Charles546 committed Jun 29, 2023
1 parent f8ce79a commit 77324d7
Show file tree
Hide file tree
Showing 8 changed files with 80 additions and 33 deletions.
19 changes: 11 additions & 8 deletions cmd/honeydipper/configcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,12 @@ func checkAuthRules(cfg *config.Config) int {
Models []interface{}
Policies []interface{}
Tests []struct {
Name string
Subject string
Object string
Action string
Result bool
Name string
Subject string
Object string
Action string
Provider string
Result bool
}
}
var tests AuthTests
Expand Down Expand Up @@ -200,7 +201,7 @@ func checkAuthRules(cfg *config.Config) int {
failedTests := []int{}
processed := 0
for i, test := range tests.Tests {
ok, err := l.Enforce(test.Subject, test.Object, test.Action)
ok, err := l.Enforce(test.Subject, test.Object, test.Action, test.Provider)
if err != nil {
e = err
processed = i
Expand All @@ -218,23 +219,25 @@ func checkAuthRules(cfg *config.Config) int {
for _, num := range failedTests {
test := tests.Tests[num]
fmt.Printf(
"%s: Sub: %s, Obj: %s, Act: %s, Expected: %t, Found: %t\n",
"%s: Sub: %s, Obj: %s, Act: %s, Provider: %s, Expected: %t, Found: %t\n",
aurora.Yellow(test.Name),
test.Subject,
test.Object,
test.Action,
test.Provider,
aurora.BrightYellow(test.Result),
aurora.Red(!test.Result),
)
}
if e != nil {
test := tests.Tests[processed]
fmt.Printf(
"%s: Sub: %s, Obj: %s, Act: %s, Expected: %t, Error: %+v\n",
"%s: Sub: %s, Obj: %s, Act: %s, Provider: %s, Expected: %t, Error: %+v\n",
aurora.Yellow(test.Name),
test.Subject,
test.Object,
test.Action,
test.Provider,
test.Result,
aurora.Red(e),
)
Expand Down
16 changes: 1 addition & 15 deletions cmd/honeydipper/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"testing"
"time"

"github.com/go-git/go-git/v5"
"github.com/honeydipper/honeydipper/internal/config"
"github.com/honeydipper/honeydipper/internal/daemon"
"github.com/honeydipper/honeydipper/internal/service"
Expand Down Expand Up @@ -59,24 +58,11 @@ func intTestDaemonStartup(t *testing.T) {
}
dipper.GetLogger("test", "INFO", logFile, logFile)
}
workingBranch, ok := os.LookupEnv("CIRCLE_BRANCH")
if !ok {
repo, err := git.PlainOpen("../..")
if err != nil {
panic(err)
}
currentBranch, err := repo.Head()
if err != nil {
panic(err)
}
ref := strings.Split(string(currentBranch.Name()), "/")
workingBranch = ref[len(ref)-1]
}
cfg = config.Config{
Services: []string{"engine", "receiver", "operator", "api"},
InitRepo: config.RepoInfo{
Repo: "../..",
Branch: workingBranch,
Branch: "",
Path: "/cmd/honeydipper/test_fixtures/bootstrap",
},
}
Expand Down
8 changes: 4 additions & 4 deletions cmd/honeydipper/test_fixtures/bootstrap/daemon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,20 +69,20 @@ drivers:
models:
- |
[request_definition]
r = sub, obj, act
r = sub, obj, act, provider
[policy_definition]
p = sub, obj, act
p = sub, obj, act, provider
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && (r.obj == p.obj || p.obj == "*") && (r.act == p.act || p.act == "*")
m = r.sub == p.sub && (r.obj == p.obj || p.obj == "*") && (r.act == p.act || p.act == "*") && r.provider == p.provider
policies:
- |
# define basic policy effects
p, admin, *, *
p, admin, *, *, auth-simple
listener:
addr: ":9100"
Binary file added drivers/cmd/auth-gcp-iap/auth-gcp-iap
Binary file not shown.
53 changes: 53 additions & 0 deletions drivers/cmd/auth-gcp-iap/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Copyright 2023 PayPal Inc.

// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT License was not distributed with this file,
// you can obtain one at https://mit-license.org/.

// Package auth-gcp-iap enables Honeydipper to authenticate/authorize incoming web requests through GCP IAP.
package main

import (
"context"
"flag"
"fmt"
"os"

"github.com/honeydipper/honeydipper/pkg/dipper"
"google.golang.org/api/idtoken"
)

func initFlags() {
flag.Usage = func() {
fmt.Printf("%s [ -h ] <service name>\n", os.Args[0])
fmt.Printf(" This driver supports receiver and API service.")
fmt.Printf(" This program provides honeydipper with the capability of authenticating the web request with gcloud IAP.")
}
}

var driver *dipper.Driver

func main() {
initFlags()
flag.Parse()

driver = dipper.NewDriver(os.Args[1], "auth-gcp-iap")
driver.RPCHandlers["auth_web_request"] = authWebRequest
driver.Reload = func(*dipper.Message) {}
driver.Run()
}

func authWebRequest(m *dipper.Message) {
m = dipper.DeserializePayload(m)
driver.GetLogger().Debugf("payloads are: %+v", m.Payload)
token := dipper.InterpolateStr("$headers.X-Goog-Iap-Jwt-Assertion.0,headers.x-goog-iap-jwt-assertion.0", m.Payload)
audience := dipper.MustGetMapDataStr(driver.Options, "data.audience")

payload := dipper.Must(idtoken.Validate(context.Background(), token, audience)).(*idtoken.Payload)
driver.GetLogger().Debugf("claims are: %+v", payload.Claims)
subject := dipper.MustGetMapDataStr(payload.Claims, "email")

m.Reply <- dipper.Message{
Payload: subject,
}
}
2 changes: 2 additions & 0 deletions internal/api/request_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

type RequestTestCase struct {
Subject string
Provider string
ContentType string `json:"content-type" mapstructure:"content-type"`
Path string
Payload map[string]interface{}
Expand Down Expand Up @@ -61,6 +62,7 @@ func requestTest(t *testing.T, caseName string) (*Store, *RequestTestCase) {

mockReqCtx := mock_api.NewMockRequestContext(ctrl)
mockReqCtx.EXPECT().Get(gomock.Eq("subject")).Times(1).Return(c.Subject, c.Subject != "")
mockReqCtx.EXPECT().Get(gomock.Eq("provider")).Times(1).Return(c.Provider, c.Provider != "")
if c.ShouldAuthorize {
mockReqCtx.EXPECT().GetPath().Times(1).Return(c.Path)
mockReqCtx.EXPECT().GetPayload(gomock.Eq(c.Def.Method)).Times(1).Return(c.Payload)
Expand Down
6 changes: 4 additions & 2 deletions internal/api/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,7 @@ func (l *Store) AuthMiddleware() gin.HandlerFunc {
allErrors[p.(string)] = err.Error()
} else {
c.Set("subject", dipper.DeserializeContent(subject))
c.Set("provider", provider)
c.Next()

return
Expand All @@ -226,9 +227,10 @@ func (l *Store) Authorize(c RequestContext, def Def) bool {
if !ok {
return false
}
provider, _ := c.Get("provider")

dipper.Logger.Warningf("'%s' , '%s', '%s'", subject, def.Object, def.Method)
if res, err := l.enforcer.Enforce(subject.(string), def.Object, def.Method); res && err == nil {
dipper.Logger.Warningf("'%s' , '%s', '%s', '%s'", subject, def.Object, def.Method, provider)
if res, err := l.enforcer.Enforce(subject.(string), def.Object, def.Method, provider.(string)); res && err == nil {
return true
} else if err != nil {
dipper.Logger.Warningf("[api] denied access with enforcer error: %+v", err)
Expand Down
9 changes: 5 additions & 4 deletions internal/api/test_fixtures/common.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,27 +6,28 @@ config:
models:
- |-
[request_definition]
r = sub, obj, act
r = sub, obj, act, provider
[policy_definition]
p = sub, obj, act
p = sub, obj, act, provider
[policy_effect]
e = some(where (p.eft == allow))
[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act && r.provider == p.provider
policies:
- |-
p, test, event, GET
p, test, event, GET, auth-simple
# mock uuids
uuids:
- 34ik-ijo3i4jt84932-aiau3kegkjrl

# mock incoming call
subject: test
provider: auth-simple
content-type: application/json
payload: {}

Expand Down

0 comments on commit 77324d7

Please sign in to comment.