Skip to content

Commit

Permalink
feat: add OIDC key feature to control plane
Browse files Browse the repository at this point in the history
  • Loading branch information
kumo-rn5s committed Jul 5, 2024
1 parent d772b5d commit d13a66b
Show file tree
Hide file tree
Showing 9 changed files with 1,015 additions and 148 deletions.
32 changes: 18 additions & 14 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ require (
github.com/aws/aws-sdk-go-v2/service/lambda v1.30.2
github.com/aws/aws-sdk-go-v2/service/s3 v1.31.0
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.18.0
github.com/coreos/go-oidc/v3 v3.9.0
github.com/creasty/defaults v1.6.0
github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f
github.com/envoyproxy/protoc-gen-validate v0.10.1
Expand All @@ -26,7 +27,7 @@ require (
github.com/goccy/go-yaml v1.9.8
github.com/golang-jwt/jwt v3.2.1+incompatible
github.com/golang/mock v1.6.0
github.com/golang/protobuf v1.5.3
github.com/golang/protobuf v1.5.4
github.com/gomodule/redigo v2.0.0+incompatible
github.com/google/go-github/v29 v29.0.3
github.com/google/uuid v1.3.0
Expand All @@ -46,13 +47,13 @@ require (
github.com/stretchr/testify v1.8.3
go.uber.org/atomic v1.7.0
go.uber.org/zap v1.10.1-0.20190709142728-9a9fa7d4b5f0
golang.org/x/crypto v0.21.0
golang.org/x/crypto v0.22.0
golang.org/x/net v0.23.0
golang.org/x/oauth2 v0.7.0
golang.org/x/sync v0.1.0
google.golang.org/api v0.116.0
golang.org/x/oauth2 v0.20.0
golang.org/x/sync v0.2.0
google.golang.org/api v0.126.0
google.golang.org/grpc v1.56.3
google.golang.org/protobuf v1.33.0
google.golang.org/protobuf v1.34.0
istio.io/api v0.0.0-20200710191538-00b73d23c685
k8s.io/api v0.24.3
k8s.io/apimachinery v0.24.3
Expand All @@ -61,9 +62,8 @@ require (
)

require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go v0.110.2 // indirect
cloud.google.com/go/compute/metadata v0.3.0 // indirect
cloud.google.com/go/iam v0.13.0 // indirect
cloud.google.com/go/longrunning v0.4.1 // indirect
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
Expand Down Expand Up @@ -110,6 +110,7 @@ require (
github.com/fatih/color v1.10.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
github.com/form3tech-oss/jwt-go v3.2.3+incompatible // indirect
github.com/go-jose/go-jose/v3 v3.0.1 // indirect
github.com/go-logr/logr v1.2.0 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect
github.com/go-openapi/jsonreference v0.19.5 // indirect
Expand All @@ -121,9 +122,10 @@ require (
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/gofuzz v1.1.0 // indirect
github.com/google/pprof v0.0.0-20221103000818-d260c55eee4c // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/googleapis/gax-go/v2 v2.8.0 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/gorilla/handlers v1.5.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
Expand Down Expand Up @@ -169,13 +171,15 @@ require (
github.com/zclconf/go-cty v1.1.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/multierr v1.2.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/term v0.18.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.1.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20230530153820-e85fd2cbaebc // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20230530153820-e85fd2cbaebc // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/ini.v1 v1.57.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
Expand Down
82 changes: 55 additions & 27 deletions go.sum

Large diffs are not rendered by default.

54 changes: 43 additions & 11 deletions pkg/app/server/httpapi/callback.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"encoding/hex"
"fmt"
"net/http"
"strings"
"time"

"go.uber.org/zap"
Expand All @@ -28,25 +29,30 @@ import (
"github.com/pipe-cd/pipecd/pkg/jwt"
"github.com/pipe-cd/pipecd/pkg/model"
"github.com/pipe-cd/pipecd/pkg/oauth/github"
"github.com/pipe-cd/pipecd/pkg/oauth/oidc"
)

func (h *authHandler) handleCallback(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")

// Validate request's payload.
projectID := r.FormValue(projectFormKey)
if projectID == "" {
h.handleError(w, r, "Missing project id", nil)

// split the project ID from the state, if it exists.
// This is necessary because some providers don't support passing the project ID in the query parameters.
state, projectID, err := parseProjectAndState(r)
if err != nil {
h.handleError(w, r, err.Error(), nil)
return
}
authCode := r.FormValue(authCodeFormKey)
if authCode == "" {
h.handleError(w, r, "Missing auth code", nil)

if err := checkState(r, h.stateKey, state); err != nil {
h.handleError(w, r, "Unauthorized access", err)
return
}

if err := checkState(r, h.stateKey); err != nil {
h.handleError(w, r, "Unauthorized access", err)
authCode := r.FormValue(authCodeFormKey)
if authCode == "" {
h.handleError(w, r, "Missing auth code", nil)
return
}

Expand Down Expand Up @@ -112,8 +118,7 @@ func (h *authHandler) handleCallback(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, rootPath, http.StatusFound)
}

func checkState(r *http.Request, key string) error {
state := r.FormValue(stateFormKey)
func checkState(r *http.Request, key string, state string) error {
rawStateToken, err := hex.DecodeString(state)
if err != nil {
return err
Expand Down Expand Up @@ -148,8 +153,35 @@ func getUser(ctx context.Context, sso *model.ProjectSSOConfig, project *model.Pr
return nil, err
}
return cli.GetUser(ctx)

case model.ProjectSSOConfig_OIDC:
if sso.Oidc == nil {
return nil, fmt.Errorf("missing OIDC oauth in the SSO configuration")
}
cli, err := oidc.NewOAuthClient(ctx, sso.Oidc, project, code)
if err != nil {
return nil, err
}
return cli.GetUser(ctx, sso.Oidc.ClientId)
default:
return nil, fmt.Errorf("not implemented")
}
}

func parseProjectAndState(r *http.Request) (string, string, error) {
state := r.FormValue(stateFormKey)
if state == "" {
return "", "", fmt.Errorf("missing state")
}

// When using OIDC SSO, the state is in the format of "state-token:project-id".
s := strings.Split(state, ":")
if len(s) != 2 {
projectID := r.FormValue(projectFormKey)
if projectID == "" {
return "", "", fmt.Errorf("missing project id")
}
return state, projectID, nil
} else {
return s[0], s[1], nil
}
}
35 changes: 35 additions & 0 deletions pkg/model/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
package model

import (
"context"
"crypto/subtle"
"fmt"
"net/url"

"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/crypto/bcrypt"
"golang.org/x/oauth2"
"golang.org/x/oauth2/github"
Expand Down Expand Up @@ -261,6 +263,11 @@ func (p *ProjectSSOConfig) GenerateAuthCodeURL(project, callbackURL, state strin
return "", fmt.Errorf("missing GitHub oauth in the SSO configuration")
}
return p.Github.GenerateAuthCodeURL(project, callbackURL, state)
case ProjectSSOConfig_OIDC:
if p.Oidc == nil {
return "", fmt.Errorf("missing OIDC oauth in the SSO configuration")
}
return p.Oidc.GenerateAuthCodeURL(project, state)

default:
return "", fmt.Errorf("not implemented")
Expand Down Expand Up @@ -348,6 +355,34 @@ func (p *ProjectSSOConfig_GitHub) GenerateAuthCodeURL(project, callbackURL, stat
return authURL, nil
}

// GenerateAuthCodeURL generates an auth URL for the specified configuration.
func (p *ProjectSSOConfig_Oidc) GenerateAuthCodeURL(project, state string) (string, error) {
ctx := context.Background()
provider, err := oidc.NewProvider(ctx, p.Issuer)
if err != nil {
return "", err
}

scopes := []string{}
if len(p.Scopes) == 0 {
scopes = append(scopes, oidc.ScopeOpenID)
} else {
scopes = p.Scopes
}

cfg := oauth2.Config{
ClientID: p.ClientId,
Endpoint: provider.Endpoint(),
Scopes: scopes,
RedirectURL: p.RedirectUri,
}

state = fmt.Sprintf("%s:%s", state, project)
authURL := cfg.AuthCodeURL(state, oauth2.ApprovalForce, oauth2.AccessTypeOnline)

return authURL, nil
}

// HasRBACRole checks whether the RBAC role is exists.
func (p *Project) HasRBACRole(name string) bool {
for _, v := range p.RbacRoles {
Expand Down
Loading

0 comments on commit d13a66b

Please sign in to comment.