├── .gitignore ├── .golangci.yml ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── adapter ├── adapter.go ├── adapter_test.go ├── authserver │ ├── authserver.go │ ├── authserver_test.go │ └── keyset │ │ ├── key.go │ │ ├── key_test.go │ │ ├── keyset.go │ │ └── keyset_test.go ├── client │ ├── client.go │ └── client_test.go ├── config │ ├── config.go │ └── intoptions.go ├── errors │ ├── oautherror.go │ └── oautherror_test.go ├── networking │ └── networking.go ├── pkg │ ├── apis │ │ └── policies │ │ │ ├── register.go │ │ │ └── v1 │ │ │ ├── doc.go │ │ │ ├── jwt_config.go │ │ │ ├── oidc_config.go │ │ │ ├── policy.go │ │ │ ├── register.go │ │ │ ├── target.go │ │ │ ├── type.go │ │ │ └── zz_generated.deepcopy.go │ └── client │ │ ├── clientset │ │ └── versioned │ │ │ ├── clientset.go │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── clientset_generated.go │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ ├── scheme │ │ │ ├── doc.go │ │ │ └── register.go │ │ │ └── typed │ │ │ └── policies │ │ │ └── v1 │ │ │ ├── doc.go │ │ │ ├── fake │ │ │ ├── doc.go │ │ │ ├── fake_jwtconfig.go │ │ │ ├── fake_oidcconfig.go │ │ │ ├── fake_policies_client.go │ │ │ └── fake_policy.go │ │ │ ├── generated_expansion.go │ │ │ ├── jwtconfig.go │ │ │ ├── oidcconfig.go │ │ │ ├── policies_client.go │ │ │ └── policy.go │ │ ├── informers │ │ └── externalversions │ │ │ ├── factory.go │ │ │ ├── generic.go │ │ │ ├── internalinterfaces │ │ │ └── factory_interfaces.go │ │ │ └── policies │ │ │ ├── interface.go │ │ │ └── v1 │ │ │ ├── interface.go │ │ │ ├── jwtconfig.go │ │ │ ├── oidcconfig.go │ │ │ └── policy.go │ │ └── listers │ │ └── policies │ │ └── v1 │ │ ├── expansion_generated.go │ │ ├── jwtconfig.go │ │ ├── oidcconfig.go │ │ └── policy.go ├── policy │ ├── action.go │ ├── controller │ │ └── controller.go │ ├── engine │ │ ├── action.go │ │ ├── engine.go │ │ └── engine_test.go │ ├── handler │ │ ├── crdeventhandler │ │ │ ├── add_event.go │ │ │ ├── add_event_test.go │ │ │ ├── delete_event.go │ │ │ ├── delete_event_test.go │ │ │ ├── utils.go │ │ │ └── utils_test.go │ │ ├── handler.go │ │ └── handler_test.go │ ├── initializer │ │ └── policyinitializer.go │ ├── policy.go │ ├── service.go │ └── store │ │ ├── pathtrie │ │ ├── pathtrie.go │ │ ├── pathtrie_test.go │ │ ├── trie.go │ │ ├── utils.go │ │ └── utils_test.go │ │ └── policy │ │ ├── localstore.go │ │ ├── localstore_test.go │ │ └── policystore.go ├── strategy │ ├── api │ │ ├── api.go │ │ └── api_test.go │ ├── strategy.go │ ├── strategy_test.go │ └── web │ │ ├── utils.go │ │ ├── utils_test.go │ │ ├── web.go │ │ └── web_test.go └── validator │ ├── token.go │ ├── validator.go │ └── validator_test.go ├── bin ├── build_deploy.sh ├── build_executable.sh ├── docker_build_tag_push.sh ├── ibmcloud_login.sh └── install_tools.sh ├── cmd └── main.go ├── config ├── adapter │ ├── adapter.appidentityandaccessadapter.config.pb.html │ ├── appidentityandaccessadapter.yaml │ ├── authnz.yaml │ ├── config.pb.go │ ├── config.proto │ └── config.proto_descriptor └── template │ ├── authnZ.pb.html │ ├── template.proto │ ├── template.yaml │ ├── template_handler.gen.go │ ├── template_handler_service.descriptor_set │ ├── template_handler_service.pb.go │ ├── template_handler_service.proto │ └── template_proto.descriptor_set ├── contributing.md ├── crdcodegenerator ├── custom-boilerplate.go.txt ├── tools.go ├── update-codegen.sh └── verify-codegen.sh ├── go.mod ├── go.sum ├── helm └── appidentityandaccessadapter │ ├── .helmignore │ ├── Chart.yaml │ ├── appidentityandaccessadapter-0.5.0.tgz │ ├── index.yaml │ ├── templates │ ├── NOTES.txt │ ├── _helpers.tpl │ ├── cluster-role-binding.yaml │ ├── deployment.yaml │ ├── handler.yaml │ ├── ibmcloudappid.yaml │ ├── instance.yaml │ ├── jwt-config.yaml │ ├── keys.yaml │ ├── oidc-config.yaml │ ├── policy.yaml │ ├── rule.yaml │ ├── service.yaml │ └── template.yaml │ └── values.yaml ├── images └── istio-adapter.png ├── maintainers.md ├── samples ├── app │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── app.js │ ├── cicd.sh │ ├── package.json │ └── sample-app.yaml └── crds │ ├── samplejwtconfig.yaml │ ├── sampleoidcconfig.yaml │ ├── sampleoidcconfigwithsecretref.yaml │ ├── samplepolicy.yaml │ └── secret.yaml └── tests ├── README.md ├── fake ├── auth_server.go ├── client.go └── keyset.go ├── framework ├── appid.go ├── context.go ├── crd_manager.go ├── framework.go ├── test.go └── utils │ ├── helm.go │ ├── kube.go │ └── shell.go ├── integration ├── helm │ └── helm_test.go ├── jwt │ └── jwt_test.go ├── oidc │ └── oidc_test.go ├── sample │ └── sample_test.go └── templates │ ├── jwtconfig.yaml │ ├── oidcconfig.yaml │ └── policy.yaml ├── keys ├── key.private └── key.pub └── testdata ├── appidentityandaccessadapter.yaml ├── attributes.yaml ├── sample_operator_cfg.yaml └── template.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | bin/appidentityandaccessadapter 2 | .idea/** 3 | ./env.sh 4 | .vscode/** 5 | vendor 6 | main 7 | .DS_Store 8 | coverage.out 9 | profile.out 10 | samples/testcrds/ 11 | samples/app/package-lock.json 12 | samples/app/node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | branches: 4 | only: 5 | - master 6 | - development 7 | - travis 8 | - /\d+\.\d+\.\d+/ 9 | 10 | go: 11 | - 1.12.x 12 | - tip 13 | 14 | env: 15 | - GO111MODULE=on 16 | 17 | services: 18 | - docker 19 | 20 | matrix: 21 | # It's ok if our code fails on unstable development versions of Go. 22 | allow_failures: 23 | - go: tip 24 | # Don't wait for tip tests to finish. Mark the tests run green if the 25 | # tests pass on the stable versions of Go. 26 | fast_finish: true 27 | 28 | git: 29 | depth: 3 30 | 31 | install: true 32 | 33 | before_script: 34 | - go get golang.org/x/tools/cmd/cover 35 | - go get github.com/mattn/goveralls 36 | 37 | script: 38 | # Install all tooling 39 | - bash ./bin/install_tools.sh || travis_terminate 1; 40 | # Login to IBMCloud and export KUBECONFIG env variable. 41 | - source ./bin/ibmcloud_login.sh || travis_terminate 1; 42 | # Build executable, build docker image, and deploy to cluster 43 | - bash ./bin/build_deploy.sh || travis_terminate 1; 44 | # Run coverage on unit tests 45 | - go test -coverprofile=coverage.out ./adapter/... || travis_terminate 1; 46 | # Run all integration tests 47 | - go test -v ./tests/integration/... || travis_terminate 1; 48 | 49 | after_success: 50 | - $HOME/gopath/bin/goveralls -service=travis-ci -coverprofile=coverage.out 51 | 52 | notifications: 53 | email: 54 | on_success: always 55 | on_failure: always 56 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.8 2 | RUN apk --no-cache add ca-certificates 3 | WORKDIR /bin/ 4 | COPY bin/appidentityandaccessadapter . 5 | ENTRYPOINT [ "/bin/appidentityandaccessadapter" ] 6 | CMD [] 7 | EXPOSE 47304 -------------------------------------------------------------------------------- /adapter/adapter_test.go: -------------------------------------------------------------------------------- 1 | package adapter 2 | 3 | // Disable tests until framework can read kubeconfig 4 | import ( 5 | "context" 6 | "errors" 7 | "testing" 8 | 9 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/fake" 10 | 11 | "github.com/gogo/googleapis/google/rpc" 12 | "github.com/stretchr/testify/assert" 13 | "istio.io/istio/mixer/pkg/status" 14 | 15 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/config" 16 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 17 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/engine" 18 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/config/template" 19 | ) 20 | 21 | func TestNew(t *testing.T) { 22 | server, err := NewAppIDAdapter(config.NewConfig()) 23 | defer server.Close() 24 | assert.Nil(t, err) 25 | s := server.(*AppidAdapter) 26 | assert.NotNil(t, s.webstrategy) 27 | assert.NotNil(t, s.apistrategy) 28 | assert.NotNil(t, s.listener) 29 | assert.NotNil(t, s.engine) 30 | assert.NotNil(t, s.server) 31 | } 32 | 33 | func TestHandleAuthorization(t *testing.T) { 34 | server, err := NewAppIDAdapter(config.NewConfig()) 35 | defer server.Close() 36 | assert.Nil(t, err) 37 | mock := &mockEngine{} 38 | 39 | s := server.(*AppidAdapter) 40 | s.engine = mock 41 | 42 | tests := []struct { 43 | req *authnz.HandleAuthnZRequest 44 | status rpc.Status 45 | action *engine.Action 46 | err error 47 | }{ 48 | { 49 | req: &authnz.HandleAuthnZRequest{}, 50 | status: status.OK, 51 | action: &engine.Action{Type: policy.NONE}, 52 | err: errors.New("invalid *authnz.HandleAuthnZRequest instance format"), 53 | }, 54 | { 55 | req: &authnz.HandleAuthnZRequest{ 56 | Instance: &authnz.InstanceMsg{ 57 | Request: &authnz.RequestMsg{}, 58 | Target: &authnz.TargetMsg{}, 59 | }, 60 | }, 61 | status: status.OK, 62 | action: &engine.Action{Type: policy.NONE}, 63 | err: errors.New("invalid *authnz.HandleAuthnZRequest instance format"), 64 | }, 65 | { 66 | req: generateAuthRequest("", "/hello/"), 67 | status: status.OK, 68 | action: &engine.Action{Type: policy.NONE}, 69 | err: nil, 70 | }, 71 | { 72 | req: generateAuthRequest("bearer token1 token2", "/"), 73 | status: status.New(16), 74 | action: &engine.Action{Type: policy.JWT}, 75 | err: nil, 76 | }, 77 | { 78 | req: generateAuthRequest("", "/"), 79 | status: status.New(16), 80 | action: &engine.Action{ 81 | Type: policy.OIDC, 82 | Client: fake.NewClient(nil), 83 | }, 84 | err: nil, 85 | }, 86 | { 87 | req: generateAuthRequest("", "/"), 88 | status: status.New(16), 89 | action: nil, 90 | err: errors.New(""), 91 | }, 92 | } 93 | 94 | for _, ts := range tests { 95 | test := ts 96 | t.Run("adapter", func(t *testing.T) { 97 | t.Parallel() 98 | mock.action = test.action 99 | mock.err = test.err 100 | result, err := s.HandleAuthnZ(context.Background(), test.req) 101 | if test.err != nil { 102 | assert.EqualError(t, test.err, err.Error()) 103 | } else { 104 | assert.Nil(t, err) 105 | assert.Equal(t, test.status.Code, result.Result.Status.Code) 106 | } 107 | }) 108 | } 109 | } 110 | 111 | type mockEngine struct { 112 | action *engine.Action 113 | err error 114 | } 115 | 116 | func (m *mockEngine) Evaluate(msg *authnz.TargetMsg) (*engine.Action, error) { 117 | return m.action, m.err 118 | } 119 | 120 | func generateAuthRequest(header string, path string) *authnz.HandleAuthnZRequest { 121 | return &authnz.HandleAuthnZRequest{ 122 | Instance: &authnz.InstanceMsg{ 123 | Request: &authnz.RequestMsg{ 124 | Headers: &authnz.HeadersMsg{ 125 | Authorization: header, 126 | }, 127 | Params: &authnz.QueryParamsMsg{ 128 | Error: "", 129 | Code: "", 130 | }, 131 | }, 132 | Target: &authnz.TargetMsg{ 133 | Path: path, 134 | }, 135 | }, 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /adapter/authserver/keyset/key.go: -------------------------------------------------------------------------------- 1 | package keyset 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rsa" 6 | "encoding/base64" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "math/big" 11 | "strings" 12 | ) 13 | 14 | // A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data 15 | // structure that represents a cryptographic key (see RFC 7517). 16 | type key struct { 17 | // The "kty" (key type) parameter identifies the cryptographic algorithm 18 | // family used with the key, such as "RSA" or "EC". 19 | Kty string `json:"kty"` 20 | 21 | // The "use" (public key use) parameter identifies the intended use of 22 | // the public key. The "use" parameter is employed to indicate whether 23 | // a public key is used for encrypting data or verifying the signature 24 | // on data. 25 | Use string `json:"use,omitempty"` 26 | 27 | // The "kid" (key ID) parameter is used to match a specific key. This 28 | // is used, for instance, to choose among a set of keys within a JWK Set 29 | // during key rollover. 30 | Kid string `json:"kid,omitempty"` 31 | 32 | // The "alg" (algorithm) parameter identifies the algorithm intended for 33 | // use with the key. 34 | Alg string `json:"alg,omitempty"` 35 | 36 | Crv string `json:"crv,omitempty"` // EC Curve 37 | X string `json:"x,omitempty"` // EC x coordinate 38 | Y string `json:"y,omitempty"` // EC y coordinate 39 | D string `json:"d,omitempty"` // RSA private exponent 40 | N string `json:"n,omitempty"` // RSA modulus 41 | E string `json:"e,omitempty"` // RSA public exponent 42 | K string `json:"k,omitempty"` // oct key 43 | } 44 | 45 | // A JSON Web Key Set (JWK Set) is a JavaScript Object Notation (JSON) data 46 | // structure that represents a set of cryptographic key (see RFC 7517). 47 | type keySet struct { 48 | // The 'keys' attribute is mandatory 49 | Keys []key `json:"keys"` 50 | // optional attributes may also be present but are currently ignored 51 | } 52 | 53 | // Decode as a public key 54 | func (k *key) decodePublicKey() (crypto.PublicKey, error) { 55 | // The "kty" (key type) parameter identifies the cryptographic algorithm 56 | // family used with the key, such as "RSA" or "EC". "kty" values should 57 | // either be registered in the IANA "JSON Web Key Types" registry 58 | // established by [JWA] or be a value that contains a Collision- 59 | // Resistant Name. The "kty" value is a case-sensitive string. 60 | switch k.Kty { 61 | case "RSA": 62 | if k.N == "" || k.E == "" { 63 | return nil, errors.New("malformed JWK RSA key") 64 | } 65 | 66 | // decode exponent 67 | data, err := safeDecode(k.E) 68 | if err != nil { 69 | return nil, errors.New("malformed JWK RSA key exponent") 70 | } 71 | if len(data) < 4 { 72 | ndata := make([]byte, 4) 73 | copy(ndata[4-len(data):], data) 74 | data = ndata 75 | } 76 | 77 | pubKey := &rsa.PublicKey{ 78 | N: &big.Int{}, 79 | E: int(binary.BigEndian.Uint32(data)), 80 | } 81 | 82 | data, err = safeDecode(k.N) 83 | if err != nil { 84 | return nil, errors.New("malformed JWK RSA key modulus") 85 | } 86 | pubKey.N.SetBytes(data) 87 | 88 | return pubKey, nil 89 | default: 90 | return nil, fmt.Errorf("unknown JWK key type %s", k.Kty) 91 | } 92 | } 93 | 94 | func safeDecode(str string) ([]byte, error) { 95 | lenMod4 := len(str) % 4 96 | if lenMod4 > 0 { 97 | str += strings.Repeat("=", 4-lenMod4) 98 | } 99 | 100 | integer, err := base64.URLEncoding.DecodeString(str) // RFC 7517 compliant encoding 101 | if err != nil { // compensate for APPID and IAM services use base64 instead of base64url 102 | return base64.StdEncoding.DecodeString(str) 103 | } 104 | return integer, err 105 | 106 | } 107 | -------------------------------------------------------------------------------- /adapter/authserver/keyset/key_test.go: -------------------------------------------------------------------------------- 1 | package keyset 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | const ( 8 | rsaKTY = "RSA" 9 | rsaN = "ALePj2tZTsUDtGlBKMPU1GjbdpVdKPITqDyLM4YhktHzrB2tt690Sdkr5g8wTFflhMEsNARxQnDr7ZywIgsCvpAqv8JSzuoIu-N8hp3FJeGvMJ_4Fh7mlrxh_KVE7Xv1zbqCGSrmsiWsA-Y0Fxt4QEcPlPd_BDh1W7_vm5WuP0sCNsclziq9t7UIrIrvHXFRA9nuxMsM2OfaisU0T9PczfO16EuJW6jflmP6J3ewoJ1AT1SbX7e98ecyD2Ke5I0ta33yk7AVCLtzubJz2NCDGPTWRivqFC0J1OkV90jzme4Eo7zs-CDK-ItVCkV4mgX6Caknd_j2hucGN4fMUDviWwE" 10 | rsaE = "AQAB" 11 | ) 12 | 13 | /////// Public key decoding happy flows ////// 14 | func TestDecodeRSAPublicKey(t *testing.T) { 15 | k := &key{Kty: rsaKTY, N: rsaN, E: rsaE} 16 | key, err := k.decodePublicKey() 17 | if err != nil { 18 | t.Errorf("Could not decode public key : %s", err) 19 | } 20 | if key == nil { 21 | t.Errorf("Expected public key to be returned") 22 | } 23 | } 24 | 25 | /////// Public key decoding unhappy flows ////// 26 | 27 | func TestDecodeUnknownPublicKeyType(t *testing.T) { 28 | k := &key{ 29 | Kty: "other", 30 | } 31 | _, err := k.decodePublicKey() 32 | if err == nil || err.Error() != "unknown JWK key type other" { 33 | t.Errorf("Expected unknown public key type : %s", err) 34 | } 35 | } 36 | 37 | func TestDecodeRSAMissingFields(t *testing.T) { 38 | k := &key{Kty: rsaKTY, N: "", E: "a"} 39 | _, err := k.decodePublicKey() 40 | if err == nil || err.Error() != "malformed JWK RSA key" { 41 | t.Errorf("Expected to receive malformed JWK error : %s", err) 42 | } 43 | 44 | k.E = "" 45 | k.N = "something" 46 | _, err = k.decodePublicKey() 47 | if err == nil || err.Error() != "malformed JWK RSA key" { 48 | t.Errorf("Expected to receive malformed JWK error : %s", err) 49 | } 50 | } 51 | 52 | func TestDecodeRSAInvalidN(t *testing.T) { 53 | k := &key{Kty: rsaKTY, N: "!", E: rsaE} 54 | _, err := k.decodePublicKey() 55 | if err == nil || err.Error() != "malformed JWK RSA key modulus" { 56 | t.Errorf("Expected to receive malformed JWK error : %s", err) 57 | } 58 | } 59 | 60 | func TestDecodeRSAInvalidE(t *testing.T) { 61 | k := &key{Kty: rsaKTY, N: rsaN, E: "?"} 62 | _, err := k.decodePublicKey() 63 | if err == nil || err.Error() != "malformed JWK RSA key exponent" { 64 | t.Errorf("Expected to receive malformed JWK error : %s", err) 65 | } 66 | } 67 | 68 | /////// Public key safe decoding ////// 69 | 70 | func TestSafeDecodeURLEncoded(t *testing.T) { 71 | bytes, err := safeDecode(rsaN) 72 | if err != nil { 73 | t.Errorf("Could not decode URL encoded public key : %s", err) 74 | } 75 | if len(bytes) != 257 { 76 | t.Errorf("Improperly decoded key : %d", len(bytes)) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /adapter/authserver/keyset/keyset.go: -------------------------------------------------------------------------------- 1 | // Package keyset contains entities to control JSON Web Key Sets (JWKS) 2 | package keyset 3 | 4 | import ( 5 | "crypto" 6 | "fmt" 7 | "net/http" 8 | 9 | "go.uber.org/zap" 10 | 11 | "golang.org/x/sync/singleflight" 12 | 13 | cstmErrs "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/errors" 14 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/networking" 15 | ) 16 | 17 | // KeySet retrieves public keys from OAuth server 18 | type KeySet interface { 19 | PublicKeyURL() string 20 | PublicKey(kid string) crypto.PublicKey 21 | } 22 | 23 | // RemoteKeySet manages the retrieval and storage of OIDC public keys 24 | type RemoteKeySet struct { 25 | publicKeyURL string 26 | httpClient *networking.HTTPClient 27 | 28 | requestGroup singleflight.Group 29 | publicKeys map[string]crypto.PublicKey 30 | } 31 | 32 | ////////////////// constructor ////////////////////////// 33 | 34 | // New creates a new Public Key Util 35 | func New(publicKeyURL string, httpClient *networking.HTTPClient) KeySet { 36 | pku := RemoteKeySet{ 37 | publicKeyURL: publicKeyURL, 38 | httpClient: httpClient, 39 | } 40 | 41 | if httpClient == nil { 42 | pku.httpClient = networking.New() 43 | } 44 | 45 | err := pku.updateKeysGrouped() 46 | if err != nil { 47 | zap.L().Debug("Error loading public keys for url. Will retry later.", zap.String("url", publicKeyURL)) 48 | return &pku 49 | } 50 | zap.L().Info("Synced JWKs successfully.", zap.String("url", publicKeyURL)) 51 | return &pku 52 | } 53 | 54 | ////////////////// instance methods ////////////////////////// 55 | 56 | // PublicKey returns the public key with the specified kid 57 | func (s *RemoteKeySet) PublicKey(kid string) crypto.PublicKey { 58 | if key := s.publicKeys[kid]; key != nil { 59 | return key 60 | } 61 | _ = s.updateKeysGrouped() 62 | return s.publicKeys[kid] 63 | } 64 | 65 | // PublicKeyURL returns the public key url for the instance 66 | func (s *RemoteKeySet) PublicKeyURL() string { 67 | return s.publicKeyURL 68 | } 69 | 70 | // updateKeyGroup issues /publicKeys request using shared request group 71 | func (s *RemoteKeySet) updateKeysGrouped() error { 72 | _, err, _ := s.requestGroup.Do(s.publicKeyURL, s.updateKeys) 73 | 74 | if err != nil { 75 | zap.L().Debug("An error occurred requesting public keys", zap.Error(err)) 76 | return err 77 | } 78 | 79 | return nil 80 | } 81 | 82 | // updateKeys retrieves public keys from the OIDC server for the instance 83 | func (s *RemoteKeySet) updateKeys() (interface{}, error) { 84 | 85 | req, err := http.NewRequest("GET", s.publicKeyURL, nil) 86 | if err != nil { 87 | zap.L().Warn("Failed to create public key request", zap.String("url", s.publicKeyURL)) 88 | return nil, err 89 | } 90 | 91 | ks := new(keySet) 92 | oa2Err := new(cstmErrs.OAuthError) 93 | if res, err := s.httpClient.Do(req, ks, oa2Err); err != nil { 94 | zap.L().Info("Failed to retrieve public keys", zap.String("url", s.publicKeyURL), zap.Error(err)) 95 | return nil, err 96 | } else if res.StatusCode != http.StatusOK { 97 | zap.L().Info("Failed to retrieve public keys", zap.String("url", s.publicKeyURL), zap.Error(oa2Err)) 98 | return nil, oa2Err 99 | } 100 | 101 | // Convert JSON keys to crypto keys 102 | keymap := make(map[string]crypto.PublicKey) 103 | for _, k := range ks.Keys { 104 | if k.Kid == "" { 105 | zap.L().Info("Invalid public key format - missing kid", zap.String("url", s.publicKeyURL), zap.Error(err)) 106 | continue 107 | } 108 | 109 | pubKey, err := k.decodePublicKey() 110 | if err != nil { 111 | zap.L().Warn("Could not decode public key err", zap.Error(err)) 112 | continue 113 | } 114 | keymap[k.Kid] = pubKey 115 | } 116 | 117 | zap.L().Info("Synced public keys", zap.String("url", s.publicKeyURL)) 118 | 119 | s.publicKeys = keymap 120 | 121 | return http.StatusOK, nil 122 | } 123 | 124 | // OK validates a KeySet Response 125 | func (k *keySet) OK() error { 126 | if k.Keys == nil { 127 | return fmt.Errorf("invalid public keys response : missing keys array") 128 | } 129 | return nil 130 | } 131 | -------------------------------------------------------------------------------- /adapter/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | 7 | "go.uber.org/zap" 8 | 9 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/authserver" 10 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 11 | ) 12 | 13 | // Client encapsulates an authn/z client object 14 | type Client interface { 15 | Name() string 16 | ID() string 17 | Secret() string 18 | Scope() string 19 | AuthorizationServer() authserver.AuthorizationServerService 20 | ExchangeGrantCode(code string, redirectURI string) (*authserver.TokenResponse, error) 21 | RefreshToken(refreshToken string) (*authserver.TokenResponse, error) 22 | } 23 | 24 | type remoteClient struct { 25 | v1.OidcConfigSpec 26 | authServer authserver.AuthorizationServerService 27 | } 28 | 29 | func (c *remoteClient) Name() string { 30 | return c.ClientName 31 | } 32 | 33 | func (c *remoteClient) ID() string { 34 | return c.ClientID 35 | } 36 | 37 | func (c *remoteClient) Secret() string { 38 | return c.ClientSecret 39 | } 40 | 41 | func (c *remoteClient) Scope() string { 42 | if len(c.Scopes) == 0 { 43 | return "openid profile email" 44 | } 45 | return strings.Join(c.Scopes, " ") 46 | } 47 | 48 | func (c *remoteClient) AuthorizationServer() authserver.AuthorizationServerService { 49 | return c.authServer 50 | } 51 | 52 | func (c *remoteClient) ExchangeGrantCode(code string, redirectURI string) (*authserver.TokenResponse, error) { 53 | if c.authServer == nil { 54 | zap.L().Error("invalid configuration :: missing authorization server", zap.String("client_name", c.ClientName)) 55 | return nil, errors.New("invalid client configuration :: missing authorization server") 56 | } 57 | return c.authServer.GetTokens(c.AuthMethod, c.ClientID, c.ClientSecret, code, redirectURI, "") 58 | } 59 | 60 | func (c *remoteClient) RefreshToken(refreshToken string) (*authserver.TokenResponse, error) { 61 | if c.authServer == nil { 62 | zap.L().Error("invalid configuration :: missing authorization server", zap.String("client_name", c.ClientName)) 63 | return nil, errors.New("invalid client configuration :: missing authorization server") 64 | } 65 | return c.authServer.GetTokens(c.AuthMethod, c.ClientID, c.ClientSecret, "", "", refreshToken) 66 | } 67 | 68 | // New creates a new client 69 | func New(cfg v1.OidcConfigSpec, s authserver.AuthorizationServerService) Client { 70 | return &remoteClient{ 71 | OidcConfigSpec: cfg, 72 | authServer: s, 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /adapter/client/client_test.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 9 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/fake" 10 | ) 11 | 12 | func TestClientNew(t *testing.T) { 13 | n := New(v1.OidcConfigSpec{ 14 | ClientSecret: "secret", 15 | ClientID: "id", 16 | ClientName: "name", 17 | }, nil) 18 | assert.NotNil(t, n) 19 | assert.Equal(t, "id", n.ID()) 20 | assert.Equal(t, "name", n.Name()) 21 | assert.Equal(t, "secret", n.Secret()) 22 | assert.Nil(t, n.AuthorizationServer()) 23 | } 24 | 25 | func TestClientTokens(t *testing.T) { 26 | c := remoteClient{ 27 | v1.OidcConfigSpec{ 28 | ClientSecret: "secret", 29 | ClientID: "id", 30 | ClientName: "name", 31 | }, 32 | nil, 33 | } 34 | 35 | res, err := c.ExchangeGrantCode("", "") 36 | assert.Nil(t, res) 37 | assert.EqualError(t, err, "invalid client configuration :: missing authorization server") 38 | 39 | res2, err2 := c.RefreshToken("") // MockServer returns nil, nil 40 | assert.Nil(t, res2) 41 | assert.EqualError(t, err2, "invalid client configuration :: missing authorization server") 42 | 43 | c.authServer = fake.NewAuthServer() 44 | res3, err3 := c.ExchangeGrantCode("", "") 45 | assert.Nil(t, res3) 46 | assert.Nil(t, err3) 47 | 48 | res4, err4 := c.RefreshToken("") 49 | assert.Nil(t, res4) 50 | assert.Nil(t, err4) 51 | } 52 | -------------------------------------------------------------------------------- /adapter/config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | // Config contains the 4 | type Config struct { 5 | // port to start the grpc adapter on 6 | AdapterPort uint16 7 | // JSON style logs 8 | Json bool 9 | // Log level - models zapcore.Level 10 | Level int8 11 | // hashKeySize used to c. 12 | // It is recommended to use a key with 32 or 64 bytes. 13 | HashKeySize IntOptions 14 | // The blockKey is used to encrypt the cookie value 15 | // Valid lengths are 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. 16 | BlockKeySize IntOptions 17 | } 18 | 19 | // defaultArgs returns the default configuration size 20 | func NewConfig() *Config { 21 | return &Config{ 22 | AdapterPort: uint16(47304), 23 | Json: false, 24 | Level: 0, 25 | HashKeySize: IntOptions{ 26 | Options: map[int]struct{}{ 27 | 32: {}, 28 | 64: {}, 29 | }, 30 | Value: 32, 31 | }, 32 | BlockKeySize: IntOptions{ 33 | Options: map[int]struct{}{ 34 | 16: {}, 35 | 24: {}, 36 | 32: {}, 37 | }, 38 | Value: 16, 39 | }, 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /adapter/config/intoptions.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // IntOptions holds Integer configuration options as part of combra command line parameters 10 | type IntOptions struct { 11 | // Options contains Enum of supported Integers 12 | Options map[int]struct{} 13 | // Value is the selected value from the enum map 14 | Value int 15 | } 16 | 17 | // String converts the core value to a string 18 | func (o *IntOptions) String() string { 19 | return strconv.Itoa(int(o.Value)) 20 | } 21 | 22 | // Set takes the user input validates it and stores it 23 | func (o *IntOptions) Set(inp string) error { 24 | i, err := strconv.ParseInt(inp, 10, 8) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | v := int(i) 30 | if _, ok := o.Options[v]; !ok { 31 | return errors.New("Expected value in " + mapKeysToString(o.Options)) 32 | } 33 | 34 | o.Value = v 35 | return nil 36 | } 37 | 38 | // Type returns the expected command line type the user must input as a string 39 | func (o *IntOptions) Type() string { 40 | return "int" 41 | } 42 | 43 | // mapKeysToString converts a map to a string array 44 | func mapKeysToString(m map[int]struct{}) string { 45 | options := "[" 46 | for v := range m { 47 | options += strconv.Itoa(int(v)) + "," 48 | } 49 | return strings.Trim(options, ",") + "]" 50 | } 51 | -------------------------------------------------------------------------------- /adapter/errors/oautherror.go: -------------------------------------------------------------------------------- 1 | // Package errors contains custom OAuth 2.0 / OIDC error objects 2 | package errors 3 | 4 | import ( 5 | "errors" 6 | "strings" 7 | 8 | "istio.io/api/policy/v1beta1" 9 | ) 10 | 11 | // HTTP Errors 12 | const ( 13 | BadRequest = "Bad Request" 14 | Unauthorized = "Unauthorized" 15 | Forbidden = "Forbidden" 16 | InternalServerError = "Internal Server Error" 17 | ) 18 | 19 | // OAuth 2.0 Errors - https://tools.ietf.org/html/rfc6750#section-3.1 20 | const ( 21 | InvalidRequest = "invalid_request" 22 | InvalidToken = "invalid_token" 23 | InsufficientScope = "insufficient_scope" 24 | ) 25 | 26 | const ( 27 | ExpiredToken = "Token is expired" 28 | ) 29 | 30 | // OAuthError - oauth error 31 | type OAuthError struct { 32 | Code string `json:"error"` 33 | Msg string `json:"error_description"` 34 | URI string `json:"error_uri"` 35 | Scopes []string `json:"scopes"` 36 | } 37 | 38 | // ExpiredTokenError creates a new expired token error 39 | func ExpiredTokenError() *OAuthError { 40 | return &OAuthError{ 41 | Code: InvalidToken, 42 | Msg: ExpiredToken, 43 | } 44 | } 45 | 46 | // UnauthorizedHTTPException creates a new invalid token error 47 | func UnauthorizedHTTPException(msg string, scopes []string) *OAuthError { 48 | return &OAuthError{ 49 | Msg: msg, 50 | Code: InvalidToken, 51 | Scopes: scopes, 52 | } 53 | } 54 | 55 | // BadRequestHTTPException creates a new invalid request error 56 | func BadRequestHTTPException(msg string) *OAuthError { 57 | return &OAuthError{ 58 | Msg: msg, 59 | Code: InvalidRequest, 60 | Scopes: nil, 61 | } 62 | } 63 | 64 | func (e *OAuthError) Error() string { 65 | if e.Code == "" && e.Msg == "" { 66 | return "an error occurred" 67 | } else if e.Code == "" { 68 | return e.Msg 69 | } else if e.Msg == "" { 70 | return e.Code 71 | } else { 72 | return e.Code + ": " + e.Msg 73 | } 74 | } 75 | 76 | // ShortDescription returns the prettified HTTP Errors 77 | func (e *OAuthError) ShortDescription() string { 78 | switch e.Code { 79 | case InvalidRequest: 80 | return BadRequest 81 | case InvalidToken: 82 | return Unauthorized 83 | case InsufficientScope: 84 | return Forbidden 85 | default: 86 | return e.Code 87 | } 88 | } 89 | 90 | // ScopeStr returns a scopes as a whitespace separated list 91 | func (e *OAuthError) ScopeStr() string { 92 | if e.Scopes == nil || len(e.Scopes) == 0 { 93 | return "" 94 | } 95 | return strings.Join(e.Scopes, " ") 96 | } 97 | 98 | // HTTPCode returns Istio compliant HTTPStatusCode 99 | func (e *OAuthError) HTTPCode() v1beta1.HttpStatusCode { 100 | switch e.Code { 101 | case InvalidRequest: 102 | return v1beta1.BadRequest 103 | case InvalidToken: 104 | return v1beta1.Unauthorized 105 | case InsufficientScope: 106 | return v1beta1.Forbidden 107 | default: 108 | return v1beta1.InternalServerError 109 | } 110 | } 111 | 112 | // HTTPCode returns Istio compliant HTTPStatusCode 113 | func (e *OAuthError) OK() error { 114 | if e.Code == "" { 115 | return errors.New("invalid OAuth 2.0 Error: `error` field does not exist") 116 | } 117 | return nil 118 | } 119 | -------------------------------------------------------------------------------- /adapter/errors/oautherror_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "errors" 5 | "github.com/stretchr/testify/assert" 6 | "istio.io/api/policy/v1beta1" 7 | "testing" 8 | ) 9 | 10 | func TestUnathorizedException(t *testing.T) { 11 | err := UnauthorizedHTTPException("my error message", []string{"scope1", "scope2"}) 12 | assert.Equal(t, InvalidToken, err.Code) 13 | assert.Equal(t, v1beta1.Unauthorized, err.HTTPCode()) 14 | assert.Equal(t, "invalid_token: my error message", err.Error()) 15 | assert.Equal(t, Unauthorized, err.ShortDescription()) 16 | assert.Equal(t, "scope1 scope2", err.ScopeStr()) 17 | } 18 | 19 | func TestBadRequestException(t *testing.T) { 20 | err := BadRequestHTTPException("my error message") 21 | assert.Equal(t, InvalidRequest, err.Code) 22 | assert.Equal(t, v1beta1.BadRequest, err.HTTPCode()) 23 | assert.Equal(t, "invalid_request: my error message", err.Error()) 24 | assert.Equal(t, BadRequest, err.ShortDescription()) 25 | assert.Equal(t, "", err.ScopeStr()) 26 | } 27 | 28 | func TestInsufficientScopeException(t *testing.T) { 29 | err := &OAuthError{Msg: "my error message", Code: InsufficientScope} 30 | assert.Equal(t, InsufficientScope, err.Code) 31 | assert.Equal(t, v1beta1.Forbidden, err.HTTPCode()) 32 | assert.Equal(t, "insufficient_scope: my error message", err.Error()) 33 | assert.Equal(t, Forbidden, err.ShortDescription()) 34 | assert.Equal(t, "", err.ScopeStr()) 35 | } 36 | 37 | func TestInternalServerException(t *testing.T) { 38 | err := &OAuthError{Msg: "my error message", Code: InternalServerError} 39 | assert.Equal(t, InternalServerError, err.Code) 40 | assert.Equal(t, v1beta1.InternalServerError, err.HTTPCode()) 41 | assert.Equal(t, "Internal Server Error: my error message", err.Error()) 42 | assert.Equal(t, InternalServerError, err.ShortDescription()) 43 | assert.Equal(t, "", err.ScopeStr()) 44 | } 45 | 46 | func TestVariants(t *testing.T) { 47 | err := &OAuthError{} 48 | assert.Equal(t, "an error occurred", err.Error()) 49 | err.Msg = "msg" 50 | assert.Equal(t, "msg", err.Error()) 51 | err.Msg = "" 52 | err.Code = "code" 53 | assert.Equal(t, "code", err.Error()) 54 | err.Msg = "msg" 55 | assert.Equal(t, "code: msg", err.Error()) 56 | } 57 | 58 | func TestExpiredTokenError(t *testing.T) { 59 | err := ExpiredTokenError() 60 | assert.Equal(t, ExpiredToken, err.Msg) 61 | assert.Equal(t, InvalidToken, err.Code) 62 | } 63 | 64 | func TestOK(t *testing.T) { 65 | err := ExpiredTokenError() 66 | assert.NoError(t, err.OK()) 67 | err = &OAuthError{} 68 | assert.Error(t, err.OK(), errors.New("invalid OAuth 2.0 Error: `error` field does not exist")) 69 | } 70 | -------------------------------------------------------------------------------- /adapter/networking/networking.go: -------------------------------------------------------------------------------- 1 | package networking 2 | 3 | import ( 4 | "encoding/json" 5 | "go.uber.org/zap" 6 | "net/http" 7 | "time" 8 | ) 9 | 10 | const ( 11 | filterType = "x-filter-type" 12 | istioAdapter = "IstioAdapter" 13 | defaultTimeout = 5 * time.Second 14 | ) 15 | 16 | // OK represents types capable of validating themselves. 17 | type OK interface { 18 | OK() error 19 | } 20 | 21 | // HTTPClient provides a wrapper for *http.Client to abstract shared responsibilities 22 | type HTTPClient struct { 23 | Client *http.Client 24 | } 25 | 26 | // New creates a new HTTPClient 27 | func New() *HTTPClient { 28 | return &HTTPClient{ 29 | &http.Client{ 30 | Timeout: defaultTimeout, 31 | }, 32 | } 33 | } 34 | 35 | // Do performs an Http request, decodes and validates the response 36 | func (c *HTTPClient) Do(req *http.Request, successV, failureV OK) (*http.Response, error) { 37 | // Append shared headers 38 | req.Header.Set(filterType, istioAdapter) 39 | 40 | // Issue original request 41 | res, err := c.Client.Do(req) 42 | if err != nil { 43 | zap.L().Info("Request failed", zap.String("url", req.URL.Path), zap.Error(err)) 44 | return res, err 45 | } 46 | 47 | defer res.Body.Close() 48 | 49 | // Decode from json 50 | if successV != nil || failureV != nil { 51 | err = decodeResponse(res, successV, failureV) 52 | } 53 | 54 | return res, err 55 | } 56 | 57 | // decodeResponse parses a response into the expected success or failure object 58 | func decodeResponse(res *http.Response, successV, failureV OK) error { 59 | if code := res.StatusCode; 200 <= code && code <= 299 { 60 | if successV != nil { 61 | return decodeJSON(res, successV) 62 | } 63 | } else { 64 | if failureV != nil { 65 | return decodeJSON(res, failureV) 66 | } 67 | } 68 | return nil 69 | } 70 | 71 | // decodeJSON parses a JSON body and calls validate 72 | func decodeJSON(r *http.Response, v OK) error { 73 | if err := json.NewDecoder(r.Body).Decode(v); err != nil { 74 | zap.L().Debug("Could not parse request body.", zap.Error(err)) 75 | return err 76 | } 77 | return v.OK() 78 | } 79 | 80 | // retry provides a recursive function retry implementation 81 | func Retry(attempts int, sleep time.Duration, fn func() (interface{}, error)) (interface{}, error) { 82 | res, err := fn() 83 | if err != nil { 84 | attempts-- 85 | if attempts > 0 { 86 | zap.L().Debug("Call failed, retrying.", zap.Int("attempts", attempts)) 87 | time.Sleep(sleep) 88 | return Retry(attempts, 2*sleep, fn) 89 | } 90 | return nil, err 91 | } 92 | return res, nil 93 | } 94 | -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/register.go: -------------------------------------------------------------------------------- 1 | package policies 2 | 3 | const GroupName = "security.cloud.ibm.com" 4 | -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/v1/doc.go: -------------------------------------------------------------------------------- 1 | // +k8s:deepcopy-gen=package 2 | // +k8s:defaulter-gen=TypeMeta 3 | // +groupName=security.cloud.ibm.com 4 | 5 | package v1 6 | -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/v1/jwt_config.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +genclient 8 | // +genclient:noStatus 9 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 10 | 11 | type JwtConfig struct { 12 | metav1.TypeMeta `json:",inline"` 13 | metav1.ObjectMeta `json:"metadata,omitempty"` 14 | 15 | Spec JwtConfigSpec `json:"spec"` 16 | } 17 | 18 | // JwtConfigSpec is the spec for a JwtConfig resource 19 | type JwtConfigSpec struct { 20 | ClientName string 21 | JwksURL string `json:"jwksUrl"` 22 | } 23 | 24 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 25 | 26 | // JwtConfigList is a list of JwtConfig resources 27 | type JwtConfigList struct { 28 | metav1.TypeMeta `json:",inline"` 29 | metav1.ListMeta `json:"metadata"` 30 | 31 | Items []JwtConfig `json:"items"` 32 | } 33 | -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/v1/oidc_config.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +genclient 8 | // +genclient:noStatus 9 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 10 | 11 | type OidcConfig struct { 12 | metav1.TypeMeta `json:",inline"` 13 | metav1.ObjectMeta `json:"metadata,omitempty"` 14 | 15 | Spec OidcConfigSpec `json:"spec"` 16 | } 17 | 18 | // OidcConfigSpec is the spec for a OidcConfig resource 19 | type OidcConfigSpec struct { 20 | ClientName string 21 | AuthMethod string `json:"authMethod"` 22 | ClientID string `json:"clientId"` 23 | DiscoveryURL string `json:"discoveryUrl"` 24 | ClientSecret string `json:"clientSecret"` 25 | ClientSecretRef ClientSecretRef `json:"clientSecretRef"` 26 | Scopes []string `json:"scopes"` 27 | } 28 | 29 | type ClientSecretRef struct { 30 | Name string `json:"name"` 31 | Key string `json:"key"` 32 | } 33 | 34 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 35 | 36 | // OidcConfigList is a list of OidcConfig resources 37 | type OidcConfigList struct { 38 | metav1.TypeMeta `json:",inline"` 39 | metav1.ListMeta `json:"metadata"` 40 | 41 | Items []OidcConfig `json:"items"` 42 | } 43 | -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/v1/policy.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 5 | ) 6 | 7 | // +genclient 8 | // +genclient:noStatus 9 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 10 | 11 | type Policy struct { 12 | metav1.TypeMeta `json:",inline"` 13 | metav1.ObjectMeta `json:"metadata,omitempty"` 14 | 15 | Spec PolicySpec `json:"spec"` 16 | } 17 | 18 | // PolicySpec is the spec for a Policy resource 19 | type PolicySpec struct { 20 | Target []TargetElement `json:"targets"` 21 | } 22 | 23 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 24 | 25 | // PolicyList is a list of Policy resources 26 | type PolicyList struct { 27 | metav1.TypeMeta `json:",inline"` 28 | metav1.ListMeta `json:"metadata"` 29 | 30 | Items []Policy `json:"items"` 31 | } 32 | -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/v1/register.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | import ( 4 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies" 5 | 6 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 7 | "k8s.io/apimachinery/pkg/runtime" 8 | "k8s.io/apimachinery/pkg/runtime/schema" 9 | ) 10 | 11 | // GroupVersion is the identifier for the API which includes 12 | // the name of the group and the version of the API 13 | var SchemeGroupVersion = schema.GroupVersion{ 14 | Group: policies.GroupName, 15 | Version: "v1", 16 | } 17 | 18 | // create a SchemeBuilder which uses functions to add types to 19 | // the scheme 20 | var ( 21 | SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) 22 | localSchemeBuilder = &SchemeBuilder 23 | AddToScheme = SchemeBuilder.AddToScheme 24 | ) 25 | 26 | func init() { 27 | // We only register manually written functions here. The registration of the 28 | // generated functions takes place in the generated files. The separation 29 | // makes the code compile even when the generated files are missing. 30 | localSchemeBuilder.Register(addKnownTypes) 31 | } 32 | 33 | // Resource takes an unqualified resource and returns a Group qualified GroupResource 34 | func Resource(resource string) schema.GroupResource { 35 | return SchemeGroupVersion.WithResource(resource).GroupResource() 36 | } 37 | 38 | // Adds the list of known types to the given scheme. 39 | func addKnownTypes(scheme *runtime.Scheme) error { 40 | scheme.AddKnownTypes( 41 | SchemeGroupVersion, 42 | &JwtConfig{}, 43 | &JwtConfigList{}, 44 | &OidcConfig{}, 45 | &OidcConfigList{}, 46 | &Policy{}, 47 | &PolicyList{}, 48 | ) 49 | 50 | scheme.AddKnownTypes(SchemeGroupVersion, 51 | &metav1.Status{}, 52 | ) 53 | // register the type in the scheme 54 | metav1.AddToGroupVersion(scheme, SchemeGroupVersion) 55 | return nil 56 | } 57 | -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/v1/target.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | type TargetElement struct { 4 | ServiceName string `json:"serviceName"` 5 | Paths []PathConfig `json:"paths"` 6 | } 7 | 8 | type PathConfig struct { 9 | Exact string `json:"exact"` 10 | Prefix string `json:"prefix"` 11 | Method string `json:"method"` 12 | Policies []PathPolicy `json:"policies"` 13 | } 14 | 15 | type PathPolicy struct { 16 | PolicyType string `json:"policyType"` 17 | Config string `json:"config"` 18 | RedirectUri string `json:"redirectUri"` 19 | Rules []Rule `json:"rules"` 20 | } 21 | 22 | type Rule struct { 23 | Claim string `json:"claim"` 24 | Values []string `json:"values"` 25 | Match string `json:"match"` 26 | Source string `json:"source"` 27 | } -------------------------------------------------------------------------------- /adapter/pkg/apis/policies/v1/type.go: -------------------------------------------------------------------------------- 1 | package v1 2 | 3 | type CrdType int 4 | 5 | const ( 6 | JWTCONFIG CrdType = iota 7 | OIDCCONFIG 8 | POLICY 9 | NONE 10 | ) 11 | 12 | func (c CrdType) String() string { 13 | return [...]string{"JwtConfig", "OidcConfig", "Policy"}[c] 14 | } -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/clientset.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package versioned 20 | 21 | import ( 22 | appidv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned/typed/policies/v1" 23 | discovery "k8s.io/client-go/discovery" 24 | rest "k8s.io/client-go/rest" 25 | flowcontrol "k8s.io/client-go/util/flowcontrol" 26 | ) 27 | 28 | type Interface interface { 29 | Discovery() discovery.DiscoveryInterface 30 | AppidV1() appidv1.AppidV1Interface 31 | } 32 | 33 | // Clientset contains the clients for groups. Each group has exactly one 34 | // version included in a Clientset. 35 | type Clientset struct { 36 | *discovery.DiscoveryClient 37 | appidV1 *appidv1.AppidV1Client 38 | } 39 | 40 | // AppidV1 retrieves the AppidV1Client 41 | func (c *Clientset) AppidV1() appidv1.AppidV1Interface { 42 | return c.appidV1 43 | } 44 | 45 | // Discovery retrieves the DiscoveryClient 46 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 47 | if c == nil { 48 | return nil 49 | } 50 | return c.DiscoveryClient 51 | } 52 | 53 | // NewForConfig creates a new Clientset for the given config. 54 | func NewForConfig(c *rest.Config) (*Clientset, error) { 55 | configShallowCopy := *c 56 | if configShallowCopy.RateLimiter == nil && configShallowCopy.QPS > 0 { 57 | configShallowCopy.RateLimiter = flowcontrol.NewTokenBucketRateLimiter(configShallowCopy.QPS, configShallowCopy.Burst) 58 | } 59 | var cs Clientset 60 | var err error 61 | cs.appidV1, err = appidv1.NewForConfig(&configShallowCopy) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return &cs, nil 71 | } 72 | 73 | // NewForConfigOrDie creates a new Clientset for the given config and 74 | // panics if there is an error in the config. 75 | func NewForConfigOrDie(c *rest.Config) *Clientset { 76 | var cs Clientset 77 | cs.appidV1 = appidv1.NewForConfigOrDie(c) 78 | 79 | cs.DiscoveryClient = discovery.NewDiscoveryClientForConfigOrDie(c) 80 | return &cs 81 | } 82 | 83 | // New creates a new Clientset for the given RESTClient. 84 | func New(c rest.Interface) *Clientset { 85 | var cs Clientset 86 | cs.appidV1 = appidv1.New(c) 87 | 88 | cs.DiscoveryClient = discovery.NewDiscoveryClient(c) 89 | return &cs 90 | } 91 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated clientset. 20 | package versioned 21 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/fake/clientset_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | clientset "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned" 23 | appidv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned/typed/policies/v1" 24 | fakeappidv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned/typed/policies/v1/fake" 25 | "k8s.io/apimachinery/pkg/runtime" 26 | "k8s.io/apimachinery/pkg/watch" 27 | "k8s.io/client-go/discovery" 28 | fakediscovery "k8s.io/client-go/discovery/fake" 29 | "k8s.io/client-go/testing" 30 | ) 31 | 32 | // NewSimpleClientset returns a clientset that will respond with the provided objects. 33 | // It's backed by a very simple object tracker that processes creates, updates and deletions as-is, 34 | // without applying any validations and/or defaults. It shouldn't be considered a replacement 35 | // for a real clientset and is mostly useful in simple unit tests. 36 | func NewSimpleClientset(objects ...runtime.Object) *Clientset { 37 | o := testing.NewObjectTracker(scheme, codecs.UniversalDecoder()) 38 | for _, obj := range objects { 39 | if err := o.Add(obj); err != nil { 40 | panic(err) 41 | } 42 | } 43 | 44 | cs := &Clientset{tracker: o} 45 | cs.discovery = &fakediscovery.FakeDiscovery{Fake: &cs.Fake} 46 | cs.AddReactor("*", "*", testing.ObjectReaction(o)) 47 | cs.AddWatchReactor("*", func(action testing.Action) (handled bool, ret watch.Interface, err error) { 48 | gvr := action.GetResource() 49 | ns := action.GetNamespace() 50 | watch, err := o.Watch(gvr, ns) 51 | if err != nil { 52 | return false, nil, err 53 | } 54 | return true, watch, nil 55 | }) 56 | 57 | return cs 58 | } 59 | 60 | // Clientset implements clientset.Interface. Meant to be embedded into a 61 | // struct to get a default implementation. This makes faking out just the method 62 | // you want to test easier. 63 | type Clientset struct { 64 | testing.Fake 65 | discovery *fakediscovery.FakeDiscovery 66 | tracker testing.ObjectTracker 67 | } 68 | 69 | func (c *Clientset) Discovery() discovery.DiscoveryInterface { 70 | return c.discovery 71 | } 72 | 73 | func (c *Clientset) Tracker() testing.ObjectTracker { 74 | return c.tracker 75 | } 76 | 77 | var _ clientset.Interface = &Clientset{} 78 | 79 | // AppidV1 retrieves the AppidV1Client 80 | func (c *Clientset) AppidV1() appidv1.AppidV1Interface { 81 | return &fakeappidv1.FakeAppidV1{Fake: &c.Fake} 82 | } 83 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated fake clientset. 20 | package fake 21 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/fake/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | appidv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | ) 29 | 30 | var scheme = runtime.NewScheme() 31 | var codecs = serializer.NewCodecFactory(scheme) 32 | var parameterCodec = runtime.NewParameterCodec(scheme) 33 | var localSchemeBuilder = runtime.SchemeBuilder{ 34 | appidv1.AddToScheme, 35 | } 36 | 37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 38 | // of clientsets, like in: 39 | // 40 | // import ( 41 | // "k8s.io/client-go/kubernetes" 42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 44 | // ) 45 | // 46 | // kclientset, _ := kubernetes.NewForConfig(c) 47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 48 | // 49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 50 | // correctly. 51 | var AddToScheme = localSchemeBuilder.AddToScheme 52 | 53 | func init() { 54 | v1.AddToGroupVersion(scheme, schema.GroupVersion{Version: "v1"}) 55 | utilruntime.Must(AddToScheme(scheme)) 56 | } 57 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/scheme/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package contains the scheme of the automatically generated clientset. 20 | package scheme 21 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/scheme/register.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package scheme 20 | 21 | import ( 22 | appidv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 23 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 24 | runtime "k8s.io/apimachinery/pkg/runtime" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | serializer "k8s.io/apimachinery/pkg/runtime/serializer" 27 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 28 | ) 29 | 30 | var Scheme = runtime.NewScheme() 31 | var Codecs = serializer.NewCodecFactory(Scheme) 32 | var ParameterCodec = runtime.NewParameterCodec(Scheme) 33 | var localSchemeBuilder = runtime.SchemeBuilder{ 34 | appidv1.AddToScheme, 35 | } 36 | 37 | // AddToScheme adds all types of this clientset into the given scheme. This allows composition 38 | // of clientsets, like in: 39 | // 40 | // import ( 41 | // "k8s.io/client-go/kubernetes" 42 | // clientsetscheme "k8s.io/client-go/kubernetes/scheme" 43 | // aggregatorclientsetscheme "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme" 44 | // ) 45 | // 46 | // kclientset, _ := kubernetes.NewForConfig(c) 47 | // _ = aggregatorclientsetscheme.AddToScheme(clientsetscheme.Scheme) 48 | // 49 | // After this, RawExtensions in Kubernetes types will serialize kube-aggregator types 50 | // correctly. 51 | var AddToScheme = localSchemeBuilder.AddToScheme 52 | 53 | func init() { 54 | v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) 55 | utilruntime.Must(AddToScheme(Scheme)) 56 | } 57 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/typed/policies/v1/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // This package has the automatically generated typed clients. 20 | package v1 21 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/typed/policies/v1/fake/doc.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | // Package fake has the automatically generated clients. 20 | package fake 21 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/typed/policies/v1/fake/fake_policies_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package fake 20 | 21 | import ( 22 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned/typed/policies/v1" 23 | rest "k8s.io/client-go/rest" 24 | testing "k8s.io/client-go/testing" 25 | ) 26 | 27 | type FakeAppidV1 struct { 28 | *testing.Fake 29 | } 30 | 31 | func (c *FakeAppidV1) JwtConfigs(namespace string) v1.JwtConfigInterface { 32 | return &FakeJwtConfigs{c, namespace} 33 | } 34 | 35 | func (c *FakeAppidV1) OidcConfigs(namespace string) v1.OidcConfigInterface { 36 | return &FakeOidcConfigs{c, namespace} 37 | } 38 | 39 | func (c *FakeAppidV1) Policies(namespace string) v1.PolicyInterface { 40 | return &FakePolicies{c, namespace} 41 | } 42 | 43 | // RESTClient returns a RESTClient that is used to communicate 44 | // with API server by this client implementation. 45 | func (c *FakeAppidV1) RESTClient() rest.Interface { 46 | var ret *rest.RESTClient 47 | return ret 48 | } 49 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/typed/policies/v1/generated_expansion.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | type JwtConfigExpansion interface{} 22 | 23 | type OidcConfigExpansion interface{} 24 | 25 | type PolicyExpansion interface{} 26 | -------------------------------------------------------------------------------- /adapter/pkg/client/clientset/versioned/typed/policies/v1/policies_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by client-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 23 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned/scheme" 24 | rest "k8s.io/client-go/rest" 25 | ) 26 | 27 | type AppidV1Interface interface { 28 | RESTClient() rest.Interface 29 | JwtConfigsGetter 30 | OidcConfigsGetter 31 | PoliciesGetter 32 | } 33 | 34 | // AppidV1Client is used to interact with features provided by the security.cloud.ibm.com group. 35 | type AppidV1Client struct { 36 | restClient rest.Interface 37 | } 38 | 39 | func (c *AppidV1Client) JwtConfigs(namespace string) JwtConfigInterface { 40 | return newJwtConfigs(c, namespace) 41 | } 42 | 43 | func (c *AppidV1Client) OidcConfigs(namespace string) OidcConfigInterface { 44 | return newOidcConfigs(c, namespace) 45 | } 46 | 47 | func (c *AppidV1Client) Policies(namespace string) PolicyInterface { 48 | return newPolicies(c, namespace) 49 | } 50 | 51 | // NewForConfig creates a new AppidV1Client for the given config. 52 | func NewForConfig(c *rest.Config) (*AppidV1Client, error) { 53 | config := *c 54 | if err := setConfigDefaults(&config); err != nil { 55 | return nil, err 56 | } 57 | client, err := rest.RESTClientFor(&config) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return &AppidV1Client{client}, nil 62 | } 63 | 64 | // NewForConfigOrDie creates a new AppidV1Client for the given config and 65 | // panics if there is an error in the config. 66 | func NewForConfigOrDie(c *rest.Config) *AppidV1Client { 67 | client, err := NewForConfig(c) 68 | if err != nil { 69 | panic(err) 70 | } 71 | return client 72 | } 73 | 74 | // New creates a new AppidV1Client for the given RESTClient. 75 | func New(c rest.Interface) *AppidV1Client { 76 | return &AppidV1Client{c} 77 | } 78 | 79 | func setConfigDefaults(config *rest.Config) error { 80 | gv := v1.SchemeGroupVersion 81 | config.GroupVersion = &gv 82 | config.APIPath = "/apis" 83 | config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() 84 | 85 | if config.UserAgent == "" { 86 | config.UserAgent = rest.DefaultKubernetesUserAgent() 87 | } 88 | 89 | return nil 90 | } 91 | 92 | // RESTClient returns a RESTClient that is used to communicate 93 | // with API server by this client implementation. 94 | func (c *AppidV1Client) RESTClient() rest.Interface { 95 | if c == nil { 96 | return nil 97 | } 98 | return c.restClient 99 | } 100 | -------------------------------------------------------------------------------- /adapter/pkg/client/informers/externalversions/generic.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package externalversions 20 | 21 | import ( 22 | "fmt" 23 | 24 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 25 | schema "k8s.io/apimachinery/pkg/runtime/schema" 26 | cache "k8s.io/client-go/tools/cache" 27 | ) 28 | 29 | // GenericInformer is type of SharedIndexInformer which will locate and delegate to other 30 | // sharedInformers based on type 31 | type GenericInformer interface { 32 | Informer() cache.SharedIndexInformer 33 | Lister() cache.GenericLister 34 | } 35 | 36 | type genericInformer struct { 37 | informer cache.SharedIndexInformer 38 | resource schema.GroupResource 39 | } 40 | 41 | // Informer returns the SharedIndexInformer. 42 | func (f *genericInformer) Informer() cache.SharedIndexInformer { 43 | return f.informer 44 | } 45 | 46 | // Lister returns the GenericLister. 47 | func (f *genericInformer) Lister() cache.GenericLister { 48 | return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) 49 | } 50 | 51 | // ForResource gives generic access to a shared informer of the matching type 52 | // TODO extend this to unknown resources with a client pool 53 | func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { 54 | switch resource { 55 | // Group=security.cloud.ibm.com, Version=v1 56 | case v1.SchemeGroupVersion.WithResource("jwtconfigs"): 57 | return &genericInformer{resource: resource.GroupResource(), informer: f.Appid().V1().JwtConfigs().Informer()}, nil 58 | case v1.SchemeGroupVersion.WithResource("oidcconfigs"): 59 | return &genericInformer{resource: resource.GroupResource(), informer: f.Appid().V1().OidcConfigs().Informer()}, nil 60 | case v1.SchemeGroupVersion.WithResource("policies"): 61 | return &genericInformer{resource: resource.GroupResource(), informer: f.Appid().V1().Policies().Informer()}, nil 62 | 63 | } 64 | 65 | return nil, fmt.Errorf("no informer found for %v", resource) 66 | } 67 | -------------------------------------------------------------------------------- /adapter/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package internalinterfaces 20 | 21 | import ( 22 | time "time" 23 | 24 | versioned "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned" 25 | v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 26 | runtime "k8s.io/apimachinery/pkg/runtime" 27 | cache "k8s.io/client-go/tools/cache" 28 | ) 29 | 30 | // NewInformerFunc takes versioned.Interface and time.Duration to return a SharedIndexInformer. 31 | type NewInformerFunc func(versioned.Interface, time.Duration) cache.SharedIndexInformer 32 | 33 | // SharedInformerFactory a small interface to allow for adding an informer without an import cycle 34 | type SharedInformerFactory interface { 35 | Start(stopCh <-chan struct{}) 36 | InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer 37 | } 38 | 39 | // TweakListOptionsFunc is a function that transforms a v1.ListOptions. 40 | type TweakListOptionsFunc func(*v1.ListOptions) 41 | -------------------------------------------------------------------------------- /adapter/pkg/client/informers/externalversions/policies/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package appid 20 | 21 | import ( 22 | internalinterfaces "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/informers/externalversions/internalinterfaces" 23 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/informers/externalversions/policies/v1" 24 | ) 25 | 26 | // Interface provides access to each of this group's versions. 27 | type Interface interface { 28 | // V1 provides access to shared informers for resources in V1. 29 | V1() v1.Interface 30 | } 31 | 32 | type group struct { 33 | factory internalinterfaces.SharedInformerFactory 34 | namespace string 35 | tweakListOptions internalinterfaces.TweakListOptionsFunc 36 | } 37 | 38 | // New returns a new Interface. 39 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 40 | return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 41 | } 42 | 43 | // V1 returns a new v1.Interface. 44 | func (g *group) V1() v1.Interface { 45 | return v1.New(g.factory, g.namespace, g.tweakListOptions) 46 | } 47 | -------------------------------------------------------------------------------- /adapter/pkg/client/informers/externalversions/policies/v1/interface.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | internalinterfaces "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/informers/externalversions/internalinterfaces" 23 | ) 24 | 25 | // Interface provides access to all the informers in this group version. 26 | type Interface interface { 27 | // JwtConfigs returns a JwtConfigInformer. 28 | JwtConfigs() JwtConfigInformer 29 | // OidcConfigs returns a OidcConfigInformer. 30 | OidcConfigs() OidcConfigInformer 31 | // Policies returns a PolicyInformer. 32 | Policies() PolicyInformer 33 | } 34 | 35 | type version struct { 36 | factory internalinterfaces.SharedInformerFactory 37 | namespace string 38 | tweakListOptions internalinterfaces.TweakListOptionsFunc 39 | } 40 | 41 | // New returns a new Interface. 42 | func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { 43 | return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} 44 | } 45 | 46 | // JwtConfigs returns a JwtConfigInformer. 47 | func (v *version) JwtConfigs() JwtConfigInformer { 48 | return &jwtConfigInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 49 | } 50 | 51 | // OidcConfigs returns a OidcConfigInformer. 52 | func (v *version) OidcConfigs() OidcConfigInformer { 53 | return &oidcConfigInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 54 | } 55 | 56 | // Policies returns a PolicyInformer. 57 | func (v *version) Policies() PolicyInformer { 58 | return &policyInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} 59 | } 60 | -------------------------------------------------------------------------------- /adapter/pkg/client/informers/externalversions/policies/v1/jwtconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | time "time" 23 | 24 | policiesv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 25 | versioned "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned" 26 | internalinterfaces "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/informers/externalversions/internalinterfaces" 27 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/listers/policies/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | runtime "k8s.io/apimachinery/pkg/runtime" 30 | watch "k8s.io/apimachinery/pkg/watch" 31 | cache "k8s.io/client-go/tools/cache" 32 | ) 33 | 34 | // JwtConfigInformer provides access to a shared informer and lister for 35 | // JwtConfigs. 36 | type JwtConfigInformer interface { 37 | Informer() cache.SharedIndexInformer 38 | Lister() v1.JwtConfigLister 39 | } 40 | 41 | type jwtConfigInformer struct { 42 | factory internalinterfaces.SharedInformerFactory 43 | tweakListOptions internalinterfaces.TweakListOptionsFunc 44 | namespace string 45 | } 46 | 47 | // NewJwtConfigInformer constructs a new informer for JwtConfig type. 48 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 49 | // one. This reduces memory footprint and number of connections to the server. 50 | func NewJwtConfigInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 51 | return NewFilteredJwtConfigInformer(client, namespace, resyncPeriod, indexers, nil) 52 | } 53 | 54 | // NewFilteredJwtConfigInformer constructs a new informer for JwtConfig type. 55 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 56 | // one. This reduces memory footprint and number of connections to the server. 57 | func NewFilteredJwtConfigInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 58 | return cache.NewSharedIndexInformer( 59 | &cache.ListWatch{ 60 | ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 61 | if tweakListOptions != nil { 62 | tweakListOptions(&options) 63 | } 64 | return client.AppidV1().JwtConfigs(namespace).List(options) 65 | }, 66 | WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 67 | if tweakListOptions != nil { 68 | tweakListOptions(&options) 69 | } 70 | return client.AppidV1().JwtConfigs(namespace).Watch(options) 71 | }, 72 | }, 73 | &policiesv1.JwtConfig{}, 74 | resyncPeriod, 75 | indexers, 76 | ) 77 | } 78 | 79 | func (f *jwtConfigInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 80 | return NewFilteredJwtConfigInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 81 | } 82 | 83 | func (f *jwtConfigInformer) Informer() cache.SharedIndexInformer { 84 | return f.factory.InformerFor(&policiesv1.JwtConfig{}, f.defaultInformer) 85 | } 86 | 87 | func (f *jwtConfigInformer) Lister() v1.JwtConfigLister { 88 | return v1.NewJwtConfigLister(f.Informer().GetIndexer()) 89 | } 90 | -------------------------------------------------------------------------------- /adapter/pkg/client/informers/externalversions/policies/v1/oidcconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | time "time" 23 | 24 | policiesv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 25 | versioned "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned" 26 | internalinterfaces "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/informers/externalversions/internalinterfaces" 27 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/listers/policies/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | runtime "k8s.io/apimachinery/pkg/runtime" 30 | watch "k8s.io/apimachinery/pkg/watch" 31 | cache "k8s.io/client-go/tools/cache" 32 | ) 33 | 34 | // OidcConfigInformer provides access to a shared informer and lister for 35 | // OidcConfigs. 36 | type OidcConfigInformer interface { 37 | Informer() cache.SharedIndexInformer 38 | Lister() v1.OidcConfigLister 39 | } 40 | 41 | type oidcConfigInformer struct { 42 | factory internalinterfaces.SharedInformerFactory 43 | tweakListOptions internalinterfaces.TweakListOptionsFunc 44 | namespace string 45 | } 46 | 47 | // NewOidcConfigInformer constructs a new informer for OidcConfig type. 48 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 49 | // one. This reduces memory footprint and number of connections to the server. 50 | func NewOidcConfigInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 51 | return NewFilteredOidcConfigInformer(client, namespace, resyncPeriod, indexers, nil) 52 | } 53 | 54 | // NewFilteredOidcConfigInformer constructs a new informer for OidcConfig type. 55 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 56 | // one. This reduces memory footprint and number of connections to the server. 57 | func NewFilteredOidcConfigInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 58 | return cache.NewSharedIndexInformer( 59 | &cache.ListWatch{ 60 | ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 61 | if tweakListOptions != nil { 62 | tweakListOptions(&options) 63 | } 64 | return client.AppidV1().OidcConfigs(namespace).List(options) 65 | }, 66 | WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 67 | if tweakListOptions != nil { 68 | tweakListOptions(&options) 69 | } 70 | return client.AppidV1().OidcConfigs(namespace).Watch(options) 71 | }, 72 | }, 73 | &policiesv1.OidcConfig{}, 74 | resyncPeriod, 75 | indexers, 76 | ) 77 | } 78 | 79 | func (f *oidcConfigInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 80 | return NewFilteredOidcConfigInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 81 | } 82 | 83 | func (f *oidcConfigInformer) Informer() cache.SharedIndexInformer { 84 | return f.factory.InformerFor(&policiesv1.OidcConfig{}, f.defaultInformer) 85 | } 86 | 87 | func (f *oidcConfigInformer) Lister() v1.OidcConfigLister { 88 | return v1.NewOidcConfigLister(f.Informer().GetIndexer()) 89 | } 90 | -------------------------------------------------------------------------------- /adapter/pkg/client/informers/externalversions/policies/v1/policy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by informer-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | time "time" 23 | 24 | policiesv1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 25 | versioned "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/clientset/versioned" 26 | internalinterfaces "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/informers/externalversions/internalinterfaces" 27 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client/listers/policies/v1" 28 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 | runtime "k8s.io/apimachinery/pkg/runtime" 30 | watch "k8s.io/apimachinery/pkg/watch" 31 | cache "k8s.io/client-go/tools/cache" 32 | ) 33 | 34 | // PolicyInformer provides access to a shared informer and lister for 35 | // Policies. 36 | type PolicyInformer interface { 37 | Informer() cache.SharedIndexInformer 38 | Lister() v1.PolicyLister 39 | } 40 | 41 | type policyInformer struct { 42 | factory internalinterfaces.SharedInformerFactory 43 | tweakListOptions internalinterfaces.TweakListOptionsFunc 44 | namespace string 45 | } 46 | 47 | // NewPolicyInformer constructs a new informer for Policy type. 48 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 49 | // one. This reduces memory footprint and number of connections to the server. 50 | func NewPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { 51 | return NewFilteredPolicyInformer(client, namespace, resyncPeriod, indexers, nil) 52 | } 53 | 54 | // NewFilteredPolicyInformer constructs a new informer for Policy type. 55 | // Always prefer using an informer factory to get a shared informer instead of getting an independent 56 | // one. This reduces memory footprint and number of connections to the server. 57 | func NewFilteredPolicyInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { 58 | return cache.NewSharedIndexInformer( 59 | &cache.ListWatch{ 60 | ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { 61 | if tweakListOptions != nil { 62 | tweakListOptions(&options) 63 | } 64 | return client.AppidV1().Policies(namespace).List(options) 65 | }, 66 | WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { 67 | if tweakListOptions != nil { 68 | tweakListOptions(&options) 69 | } 70 | return client.AppidV1().Policies(namespace).Watch(options) 71 | }, 72 | }, 73 | &policiesv1.Policy{}, 74 | resyncPeriod, 75 | indexers, 76 | ) 77 | } 78 | 79 | func (f *policyInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { 80 | return NewFilteredPolicyInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) 81 | } 82 | 83 | func (f *policyInformer) Informer() cache.SharedIndexInformer { 84 | return f.factory.InformerFor(&policiesv1.Policy{}, f.defaultInformer) 85 | } 86 | 87 | func (f *policyInformer) Lister() v1.PolicyLister { 88 | return v1.NewPolicyLister(f.Informer().GetIndexer()) 89 | } 90 | -------------------------------------------------------------------------------- /adapter/pkg/client/listers/policies/v1/expansion_generated.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | // JwtConfigListerExpansion allows custom methods to be added to 22 | // JwtConfigLister. 23 | type JwtConfigListerExpansion interface{} 24 | 25 | // JwtConfigNamespaceListerExpansion allows custom methods to be added to 26 | // JwtConfigNamespaceLister. 27 | type JwtConfigNamespaceListerExpansion interface{} 28 | 29 | // OidcConfigListerExpansion allows custom methods to be added to 30 | // OidcConfigLister. 31 | type OidcConfigListerExpansion interface{} 32 | 33 | // OidcConfigNamespaceListerExpansion allows custom methods to be added to 34 | // OidcConfigNamespaceLister. 35 | type OidcConfigNamespaceListerExpansion interface{} 36 | 37 | // PolicyListerExpansion allows custom methods to be added to 38 | // PolicyLister. 39 | type PolicyListerExpansion interface{} 40 | 41 | // PolicyNamespaceListerExpansion allows custom methods to be added to 42 | // PolicyNamespaceLister. 43 | type PolicyNamespaceListerExpansion interface{} 44 | -------------------------------------------------------------------------------- /adapter/pkg/client/listers/policies/v1/jwtconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 23 | "k8s.io/apimachinery/pkg/api/errors" 24 | "k8s.io/apimachinery/pkg/labels" 25 | "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // JwtConfigLister helps list JwtConfigs. 29 | type JwtConfigLister interface { 30 | // List lists all JwtConfigs in the indexer. 31 | List(selector labels.Selector) (ret []*v1.JwtConfig, err error) 32 | // JwtConfigs returns an object that can list and get JwtConfigs. 33 | JwtConfigs(namespace string) JwtConfigNamespaceLister 34 | JwtConfigListerExpansion 35 | } 36 | 37 | // jwtConfigLister implements the JwtConfigLister interface. 38 | type jwtConfigLister struct { 39 | indexer cache.Indexer 40 | } 41 | 42 | // NewJwtConfigLister returns a new JwtConfigLister. 43 | func NewJwtConfigLister(indexer cache.Indexer) JwtConfigLister { 44 | return &jwtConfigLister{indexer: indexer} 45 | } 46 | 47 | // List lists all JwtConfigs in the indexer. 48 | func (s *jwtConfigLister) List(selector labels.Selector) (ret []*v1.JwtConfig, err error) { 49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 50 | ret = append(ret, m.(*v1.JwtConfig)) 51 | }) 52 | return ret, err 53 | } 54 | 55 | // JwtConfigs returns an object that can list and get JwtConfigs. 56 | func (s *jwtConfigLister) JwtConfigs(namespace string) JwtConfigNamespaceLister { 57 | return jwtConfigNamespaceLister{indexer: s.indexer, namespace: namespace} 58 | } 59 | 60 | // JwtConfigNamespaceLister helps list and get JwtConfigs. 61 | type JwtConfigNamespaceLister interface { 62 | // List lists all JwtConfigs in the indexer for a given namespace. 63 | List(selector labels.Selector) (ret []*v1.JwtConfig, err error) 64 | // Get retrieves the JwtConfig from the indexer for a given namespace and name. 65 | Get(name string) (*v1.JwtConfig, error) 66 | JwtConfigNamespaceListerExpansion 67 | } 68 | 69 | // jwtConfigNamespaceLister implements the JwtConfigNamespaceLister 70 | // interface. 71 | type jwtConfigNamespaceLister struct { 72 | indexer cache.Indexer 73 | namespace string 74 | } 75 | 76 | // List lists all JwtConfigs in the indexer for a given namespace. 77 | func (s jwtConfigNamespaceLister) List(selector labels.Selector) (ret []*v1.JwtConfig, err error) { 78 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 79 | ret = append(ret, m.(*v1.JwtConfig)) 80 | }) 81 | return ret, err 82 | } 83 | 84 | // Get retrieves the JwtConfig from the indexer for a given namespace and name. 85 | func (s jwtConfigNamespaceLister) Get(name string) (*v1.JwtConfig, error) { 86 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 87 | if err != nil { 88 | return nil, err 89 | } 90 | if !exists { 91 | return nil, errors.NewNotFound(v1.Resource("jwtconfig"), name) 92 | } 93 | return obj.(*v1.JwtConfig), nil 94 | } 95 | -------------------------------------------------------------------------------- /adapter/pkg/client/listers/policies/v1/oidcconfig.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 23 | "k8s.io/apimachinery/pkg/api/errors" 24 | "k8s.io/apimachinery/pkg/labels" 25 | "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // OidcConfigLister helps list OidcConfigs. 29 | type OidcConfigLister interface { 30 | // List lists all OidcConfigs in the indexer. 31 | List(selector labels.Selector) (ret []*v1.OidcConfig, err error) 32 | // OidcConfigs returns an object that can list and get OidcConfigs. 33 | OidcConfigs(namespace string) OidcConfigNamespaceLister 34 | OidcConfigListerExpansion 35 | } 36 | 37 | // oidcConfigLister implements the OidcConfigLister interface. 38 | type oidcConfigLister struct { 39 | indexer cache.Indexer 40 | } 41 | 42 | // NewOidcConfigLister returns a new OidcConfigLister. 43 | func NewOidcConfigLister(indexer cache.Indexer) OidcConfigLister { 44 | return &oidcConfigLister{indexer: indexer} 45 | } 46 | 47 | // List lists all OidcConfigs in the indexer. 48 | func (s *oidcConfigLister) List(selector labels.Selector) (ret []*v1.OidcConfig, err error) { 49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 50 | ret = append(ret, m.(*v1.OidcConfig)) 51 | }) 52 | return ret, err 53 | } 54 | 55 | // OidcConfigs returns an object that can list and get OidcConfigs. 56 | func (s *oidcConfigLister) OidcConfigs(namespace string) OidcConfigNamespaceLister { 57 | return oidcConfigNamespaceLister{indexer: s.indexer, namespace: namespace} 58 | } 59 | 60 | // OidcConfigNamespaceLister helps list and get OidcConfigs. 61 | type OidcConfigNamespaceLister interface { 62 | // List lists all OidcConfigs in the indexer for a given namespace. 63 | List(selector labels.Selector) (ret []*v1.OidcConfig, err error) 64 | // Get retrieves the OidcConfig from the indexer for a given namespace and name. 65 | Get(name string) (*v1.OidcConfig, error) 66 | OidcConfigNamespaceListerExpansion 67 | } 68 | 69 | // oidcConfigNamespaceLister implements the OidcConfigNamespaceLister 70 | // interface. 71 | type oidcConfigNamespaceLister struct { 72 | indexer cache.Indexer 73 | namespace string 74 | } 75 | 76 | // List lists all OidcConfigs in the indexer for a given namespace. 77 | func (s oidcConfigNamespaceLister) List(selector labels.Selector) (ret []*v1.OidcConfig, err error) { 78 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 79 | ret = append(ret, m.(*v1.OidcConfig)) 80 | }) 81 | return ret, err 82 | } 83 | 84 | // Get retrieves the OidcConfig from the indexer for a given namespace and name. 85 | func (s oidcConfigNamespaceLister) Get(name string) (*v1.OidcConfig, error) { 86 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 87 | if err != nil { 88 | return nil, err 89 | } 90 | if !exists { 91 | return nil, errors.NewNotFound(v1.Resource("oidcconfig"), name) 92 | } 93 | return obj.(*v1.OidcConfig), nil 94 | } 95 | -------------------------------------------------------------------------------- /adapter/pkg/client/listers/policies/v1/policy.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | // Code generated by lister-gen. DO NOT EDIT. 18 | 19 | package v1 20 | 21 | import ( 22 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 23 | "k8s.io/apimachinery/pkg/api/errors" 24 | "k8s.io/apimachinery/pkg/labels" 25 | "k8s.io/client-go/tools/cache" 26 | ) 27 | 28 | // PolicyLister helps list Policies. 29 | type PolicyLister interface { 30 | // List lists all Policies in the indexer. 31 | List(selector labels.Selector) (ret []*v1.Policy, err error) 32 | // Policies returns an object that can list and get Policies. 33 | Policies(namespace string) PolicyNamespaceLister 34 | PolicyListerExpansion 35 | } 36 | 37 | // policyLister implements the PolicyLister interface. 38 | type policyLister struct { 39 | indexer cache.Indexer 40 | } 41 | 42 | // NewPolicyLister returns a new PolicyLister. 43 | func NewPolicyLister(indexer cache.Indexer) PolicyLister { 44 | return &policyLister{indexer: indexer} 45 | } 46 | 47 | // List lists all Policies in the indexer. 48 | func (s *policyLister) List(selector labels.Selector) (ret []*v1.Policy, err error) { 49 | err = cache.ListAll(s.indexer, selector, func(m interface{}) { 50 | ret = append(ret, m.(*v1.Policy)) 51 | }) 52 | return ret, err 53 | } 54 | 55 | // Policies returns an object that can list and get Policies. 56 | func (s *policyLister) Policies(namespace string) PolicyNamespaceLister { 57 | return policyNamespaceLister{indexer: s.indexer, namespace: namespace} 58 | } 59 | 60 | // PolicyNamespaceLister helps list and get Policies. 61 | type PolicyNamespaceLister interface { 62 | // List lists all Policies in the indexer for a given namespace. 63 | List(selector labels.Selector) (ret []*v1.Policy, err error) 64 | // Get retrieves the Policy from the indexer for a given namespace and name. 65 | Get(name string) (*v1.Policy, error) 66 | PolicyNamespaceListerExpansion 67 | } 68 | 69 | // policyNamespaceLister implements the PolicyNamespaceLister 70 | // interface. 71 | type policyNamespaceLister struct { 72 | indexer cache.Indexer 73 | namespace string 74 | } 75 | 76 | // List lists all Policies in the indexer for a given namespace. 77 | func (s policyNamespaceLister) List(selector labels.Selector) (ret []*v1.Policy, err error) { 78 | err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { 79 | ret = append(ret, m.(*v1.Policy)) 80 | }) 81 | return ret, err 82 | } 83 | 84 | // Get retrieves the Policy from the indexer for a given namespace and name. 85 | func (s policyNamespaceLister) Get(name string) (*v1.Policy, error) { 86 | obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) 87 | if err != nil { 88 | return nil, err 89 | } 90 | if !exists { 91 | return nil, errors.NewNotFound(v1.Resource("policy"), name) 92 | } 93 | return obj.(*v1.Policy), nil 94 | } 95 | -------------------------------------------------------------------------------- /adapter/policy/action.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | type Method int 4 | 5 | const ( 6 | ALL Method = iota 7 | GET 8 | PUT 9 | POST 10 | DELETE 11 | PATCH 12 | ) 13 | 14 | func (m Method) String() string { 15 | return [...]string{"ALL", "GET", "PUT", "POST", "DELETE", "PATCH"}[m] 16 | } 17 | 18 | func NewMethod(method string) Method { 19 | switch method { 20 | case "ALL": 21 | return ALL 22 | case "GET": 23 | return GET 24 | case "PUT": 25 | return PUT 26 | case "POST": 27 | return POST 28 | case "DELETE": 29 | return DELETE 30 | case "PATCH": 31 | return PATCH 32 | default: 33 | return ALL 34 | } 35 | } 36 | 37 | type Actions = map[Method]RoutePolicy 38 | 39 | // New creates a new Actions 40 | func NewActions() Actions { 41 | return make(map[Method]RoutePolicy) 42 | } 43 | -------------------------------------------------------------------------------- /adapter/policy/controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "go.uber.org/zap" 8 | 9 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 10 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 11 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/handler" 12 | 13 | utilruntime "k8s.io/apimachinery/pkg/util/runtime" 14 | "k8s.io/apimachinery/pkg/util/wait" 15 | "k8s.io/client-go/kubernetes" 16 | "k8s.io/client-go/tools/cache" 17 | "k8s.io/client-go/util/workqueue" 18 | ) 19 | 20 | // Controller struct defines how a controller should encapsulate 21 | // logging, client connectivity, informing (list and watching) 22 | // queueing, and handling of resource changes 23 | type Controller struct { 24 | CrdType v1.CrdType 25 | Clientset kubernetes.Interface 26 | Queue workqueue.RateLimitingInterface 27 | Informer cache.SharedIndexInformer 28 | Handler handler.PolicyHandler 29 | } 30 | 31 | // Run is the main path of execution for the controller loop 32 | func (c *Controller) Run(stopCh <-chan struct{}) { 33 | // handle a panic with logging and exiting 34 | defer utilruntime.HandleCrash() 35 | // ignore new items in the queue but when all goroutines 36 | // have completed existing items then shutdown 37 | defer c.Queue.ShutDown() 38 | 39 | zap.L().Debug("Controller.Run: initiating") 40 | 41 | // run the informer to start listing and watching resources 42 | go c.Informer.Run(stopCh) 43 | 44 | // do the initial synchronization (one time) to populate resources 45 | if !cache.WaitForCacheSync(stopCh, c.HasSynced) { 46 | utilruntime.HandleError(fmt.Errorf("error syncing cache")) 47 | return 48 | } 49 | zap.L().Debug("Controller.Run: cache sync complete") 50 | 51 | // run the runWorker method every second with a stop channel 52 | wait.Until(c.runWorker, time.Second, stopCh) 53 | } 54 | 55 | // HasSynced allows us to satisfy the Controller interface 56 | // by wiring up the informer's HasSynced method to it 57 | func (c *Controller) HasSynced() bool { 58 | return c.Informer.HasSynced() 59 | } 60 | 61 | // runWorker executes the loop to process new items added to the queue 62 | func (c *Controller) runWorker() { 63 | zap.L().Debug("Controller.runWorker: starting") 64 | 65 | // invoke processNextItem to fetch and consume the next change 66 | // to a watched or listed resource 67 | for c.processNextItem() { 68 | zap.L().Debug("Controller.runWorker: processing next item") 69 | } 70 | 71 | zap.L().Debug("Controller.runWorker: completed") 72 | } 73 | 74 | // processNextItem retrieves each queued item and takes the 75 | // necessary handler action based off of if the item was 76 | // created or deleted 77 | func (c *Controller) processNextItem() bool { 78 | zap.L().Debug("Controller.processNextItem: start") 79 | 80 | // fetch the next item (blocking) from the queue to process or 81 | // if a shutdown is requested then return out of this to stop 82 | // processing 83 | key, quit := c.Queue.Get() 84 | 85 | // stop the worker loop from running as this indicates we 86 | // have sent a shutdown message that the queue has indicated 87 | // from the Get method 88 | if quit { 89 | return false 90 | } 91 | 92 | defer c.Queue.Done(key) 93 | 94 | // assert the string out of the key (format `namespace/name`) 95 | keyRaw := key.(string) 96 | 97 | item, exists, err := c.Informer.GetIndexer().GetByKey(keyRaw) 98 | if err != nil { 99 | if c.Queue.NumRequeues(key) < 5 { 100 | zap.L().Error("Controller.processNextItem: Failed processing item retrying", zap.String("key", keyRaw), zap.Error(err)) 101 | c.Queue.AddRateLimited(key) 102 | } else { 103 | zap.L().Error("Controller.processNextItem: Failed processing item no more retries", zap.String("key", keyRaw), zap.Error(err)) 104 | c.Queue.Forget(key) 105 | utilruntime.HandleError(err) 106 | } 107 | } 108 | 109 | // if the item doesn't exist then it was deleted and we need to fire off the handler's 110 | // ObjectDeleted method. but if the object does exist that indicates that the object 111 | // was created (or updated) so run the ObjectCreated method 112 | // 113 | // after both instances, we want to forget the key from the queue, as this indicates 114 | // a code path of successful queue key processing 115 | if !exists { 116 | zap.L().Debug("Controller.processNextItem: object deleted detected: %s", zap.String("key", keyRaw)) 117 | c.Handler.HandleDeleteEvent(policy.CrdKey{Id: keyRaw, CrdType: c.CrdType}) 118 | c.Queue.Forget(key) 119 | } else { 120 | zap.L().Debug("Controller.processNextItem: object created detected: %s", zap.String("key", keyRaw)) 121 | c.Handler.HandleAddUpdateEvent(item) 122 | c.Queue.Forget(key) 123 | } 124 | 125 | // keep the worker loop running by returning true 126 | return true 127 | } 128 | -------------------------------------------------------------------------------- /adapter/policy/engine/action.go: -------------------------------------------------------------------------------- 1 | // Package engine is responsible for making policy decisions 2 | package engine 3 | 4 | import ( 5 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/authserver/keyset" 6 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/client" 7 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 8 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 9 | ) 10 | 11 | // Action encapsulates information needed to begin executing a policy 12 | type Action struct { 13 | v1.PathPolicy 14 | KeySet keyset.KeySet 15 | Client client.Client 16 | Type policy.Type 17 | } 18 | -------------------------------------------------------------------------------- /adapter/policy/handler/crdeventhandler/delete_event.go: -------------------------------------------------------------------------------- 1 | package crdeventhandler 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 7 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 8 | storepolicy "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/store/policy" 9 | ) 10 | 11 | type DeleteEventHandler interface { 12 | HandleDeleteEvent() 13 | } 14 | 15 | type JwtConfigDeleteEventHandler struct { 16 | Key string 17 | Store storepolicy.PolicyStore 18 | } 19 | 20 | type OidcConfigDeleteEventHandler struct { 21 | Key string 22 | Store storepolicy.PolicyStore 23 | } 24 | 25 | type PolicyDeleteEventHandler struct { 26 | Key string 27 | Store storepolicy.PolicyStore 28 | } 29 | 30 | func (e *JwtConfigDeleteEventHandler) HandleDeleteEvent() { 31 | e.Store.DeleteKeySet(e.Key) 32 | } 33 | 34 | func (e *OidcConfigDeleteEventHandler) HandleDeleteEvent() { 35 | e.Store.DeleteClient(e.Key) 36 | } 37 | 38 | func (e *PolicyDeleteEventHandler) HandleDeleteEvent() { 39 | parsedPolicies := e.Store.GetPolicyMapping(e.Key) 40 | for _, policies := range parsedPolicies { 41 | zap.S().Debug("Getting policy for endpoint", policies.Endpoint) 42 | storedPolicy := e.Store.GetPolicies(policies.Endpoint) 43 | if storedPolicy.PolicyReference == e.Key { 44 | e.Store.SetPolicies(policies.Endpoint, policy.NewRoutePolicy()) 45 | } 46 | } 47 | // remove entry from policyMapping 48 | e.Store.DeletePolicyMapping(e.Key) 49 | zap.S().Debug("Delete policy completed") 50 | } 51 | 52 | func GetDeleteEventHandler(crd policy.CrdKey, store storepolicy.PolicyStore) DeleteEventHandler { 53 | switch crd.CrdType { 54 | case v1.JWTCONFIG: 55 | return &JwtConfigDeleteEventHandler{ 56 | Key: crd.Id, 57 | Store: store, 58 | } 59 | case v1.OIDCCONFIG: 60 | return &OidcConfigDeleteEventHandler{ 61 | Key: crd.Id, 62 | Store: store, 63 | } 64 | case v1.POLICY: 65 | return &PolicyDeleteEventHandler{ 66 | Key: crd.Id, 67 | Store: store, 68 | } 69 | default: 70 | zap.S().Warn("Could not delete object. Unknown type: %f", crd) 71 | return nil 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /adapter/policy/handler/crdeventhandler/delete_event_test.go: -------------------------------------------------------------------------------- 1 | package crdeventhandler 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "k8s.io/client-go/kubernetes/fake" 8 | 9 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 10 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 11 | storePolicy "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/store/policy" 12 | ) 13 | 14 | func TestHandler_JwtConfigDeleteEventHandler(t *testing.T) { 15 | store:= storePolicy.New() 16 | handler := GetAddEventHandler(jwtConfigGenerator(), store, fake.NewSimpleClientset()) 17 | handler.HandleAddUpdateEvent() 18 | key := "ns/sample" 19 | assert.Equal(t, store.GetKeySet(key).PublicKeyURL(), jwksUrl) 20 | deleteHandler := GetDeleteEventHandler(policy.CrdKey{ Id: key, CrdType: v1.JWTCONFIG}, store) 21 | deleteHandler.HandleDeleteEvent() 22 | assert.Nil(t, store.GetKeySet(key)) 23 | } 24 | 25 | func TestHandler_OidcConfigDeleteEventHandler(t *testing.T) { 26 | store:= storePolicy.New() 27 | policyName := "oidcconfig" 28 | key := "ns/" + policyName 29 | handler := GetAddEventHandler(oidcConfigGenerator(policyName, "oidc", jwksUrl), store, fake.NewSimpleClientset()) 30 | handler.HandleAddUpdateEvent() 31 | assert.Equal(t, store.GetClient(key).Secret(), secretFromPlainText) 32 | deleteHandler := GetDeleteEventHandler(policy.CrdKey{ Id: key, CrdType: v1.OIDCCONFIG}, store) 33 | deleteHandler.HandleDeleteEvent() 34 | assert.Nil(t, store.GetClient(key)) 35 | } 36 | 37 | func TestHandler_PolicyDeleteEventHandler(t *testing.T) { 38 | store:= storePolicy.New() 39 | key := "ns/sample" 40 | targets := []v1.TargetElement{ 41 | getTargetElements(service, getPathConfigs(getPathConfig("/path", "/paths", "GET", getPathPolicy()))), 42 | } 43 | handler := GetAddEventHandler(policyGenerator(targets), store, fake.NewSimpleClientset()) 44 | handler.HandleAddUpdateEvent() 45 | assert.Equal(t, store.GetPolicies(getEndpoint(getDefaultService(), policy.GET,"/path")), getRoutePolicy(key)) 46 | assert.Equal(t, store.GetPolicies(getEndpoint(getDefaultService(), policy.GET,"/paths/*")), getRoutePolicy(key)) 47 | deleteHandler := GetDeleteEventHandler(policy.CrdKey{ Id: key, CrdType: v1.POLICY}, store) 48 | deleteHandler.HandleDeleteEvent() 49 | assert.Equal(t, store.GetPolicies(getEndpoint(getDefaultService(),policy.GET,"/path")), getDefaultRoutePolicy()) 50 | assert.Equal(t, store.GetPolicies(getEndpoint(getDefaultService(), policy.GET, "/paths/*")), getDefaultRoutePolicy()) 51 | 52 | } 53 | 54 | func TestHandler_InvalidInputObject(t *testing.T) { 55 | store:= storePolicy.New() 56 | handler := GetDeleteEventHandler(policy.CrdKey{ Id: "key", CrdType: 5}, store) 57 | assert.Nil(t, handler) 58 | } -------------------------------------------------------------------------------- /adapter/policy/handler/crdeventhandler/utils.go: -------------------------------------------------------------------------------- 1 | package crdeventhandler 2 | 3 | import ( 4 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 5 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 6 | 7 | "strings" 8 | 9 | k8sv1 "k8s.io/api/core/v1" 10 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 | "k8s.io/client-go/kubernetes" 12 | ) 13 | 14 | func getEndpoint(service policy.Service, method policy.Method, path string) policy.Endpoint { 15 | return policy.Endpoint{ 16 | Service: service, 17 | Method: method, 18 | Path: path, 19 | } 20 | } 21 | 22 | func getParsedPolicy(service policy.Service, method policy.Method, path string, policies []v1.PathPolicy) policy.PolicyMapping{ 23 | return policy.NewPolicyMapping(getEndpoint(service, method, path), policies) 24 | } 25 | 26 | func ParseTarget(target []v1.TargetElement, namespace string) []policy.PolicyMapping { 27 | targets := make([]policy.PolicyMapping, 0) 28 | if len(target) > 0 { 29 | for _, items := range target { 30 | service := policy.Service{ 31 | Name: items.ServiceName, 32 | Namespace: namespace, 33 | } 34 | if items.Paths != nil && len(items.Paths) > 0 { 35 | for _, path := range items.Paths { 36 | method := policy.NewMethod(path.Method) 37 | if path.Exact != "" { 38 | if path.Exact != "/" { 39 | path.Exact = strings.TrimRight(path.Exact, "/") 40 | } 41 | targets = append(targets, getParsedPolicy(service, method, path.Exact, path.Policies)) 42 | } 43 | 44 | if path.Prefix != "" { 45 | if !strings.HasSuffix(path.Prefix,"/*") { 46 | if strings.HasSuffix(path.Prefix,"/") { 47 | path.Prefix = path.Prefix + "*" 48 | } else { 49 | path.Prefix = path.Prefix + "/*" 50 | } 51 | } 52 | targets = append(targets, getParsedPolicy(service, method, path.Prefix, path.Policies)) 53 | } 54 | 55 | if path.Exact == "" && path.Prefix == "" { 56 | targets = append(targets, getParsedPolicy(service, method, "/*", path.Policies)) 57 | } 58 | } 59 | } 60 | } 61 | } 62 | return targets 63 | } 64 | 65 | func GetKubeSecret(kubeClient kubernetes.Interface, namespace string, ref v1.ClientSecretRef) (*k8sv1.Secret, error) { 66 | return kubeClient.CoreV1().Secrets(namespace).Get(ref.Name, metav1.GetOptions{}) 67 | } -------------------------------------------------------------------------------- /adapter/policy/handler/crdeventhandler/utils_test.go: -------------------------------------------------------------------------------- 1 | package crdeventhandler 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | 10 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 11 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 12 | ) 13 | 14 | func getPathConfig(exact string, prefix string, method string, policies []v1.PathPolicy) v1.PathConfig { 15 | return v1.PathConfig{ 16 | Exact: exact, 17 | Prefix: prefix, 18 | Method: method, 19 | Policies: policies, 20 | } 21 | } 22 | 23 | func getPathConfigs(path v1.PathConfig) []v1.PathConfig{ 24 | return []v1.PathConfig{ path,} 25 | } 26 | 27 | func getTargetElements(service string, paths []v1.PathConfig) v1.TargetElement { 28 | return v1.TargetElement{ 29 | ServiceName: service, 30 | Paths: paths, 31 | } 32 | } 33 | 34 | 35 | type output struct { 36 | policies []policy.PolicyMapping 37 | total int 38 | } 39 | 40 | func TestParsedTarget(t *testing.T) { 41 | tests := [] struct{ 42 | name string 43 | targets []v1.TargetElement 44 | output output 45 | } { 46 | { 47 | name: "No exact/prefix provided", 48 | targets: []v1.TargetElement{ 49 | getTargetElements(service, getPathConfigs(getPathConfig("", "", "", getDefaultPathPolicy()))), 50 | getTargetElements(service, getPathConfigs(getPathConfig("", "", "GET", getDefaultPathPolicy()))), 51 | }, 52 | output: output{ 53 | total: 2, 54 | policies: []policy.PolicyMapping{ 55 | { 56 | Endpoint: getEndpoint(getDefaultService(), policy.ALL, "/*"), 57 | Actions: getDefaultPathPolicy(), 58 | }, 59 | { 60 | Endpoint: getEndpoint(getDefaultService(), policy.GET, "/*"), 61 | Actions: getDefaultPathPolicy(), 62 | }, 63 | }, 64 | }, 65 | }, 66 | { 67 | name: "exact path test", 68 | targets: []v1.TargetElement{ 69 | getTargetElements(service, getPathConfigs(getPathConfig("/", "", "", getDefaultPathPolicy()))), 70 | getTargetElements(service, getPathConfigs(getPathConfig("/path", "", "GET", getDefaultPathPolicy()))), 71 | getTargetElements(service, getPathConfigs(getPathConfig("/path1/", "", "GET", getDefaultPathPolicy()))), 72 | }, 73 | output: output{ 74 | total: 3, 75 | policies: []policy.PolicyMapping{ 76 | { 77 | Endpoint: getEndpoint(getDefaultService(), policy.ALL, "/"), 78 | Actions: getDefaultPathPolicy(), 79 | }, 80 | { 81 | Endpoint: getEndpoint(getDefaultService(), policy.GET, "/path"), 82 | Actions: getDefaultPathPolicy(), 83 | }, 84 | { 85 | Endpoint: getEndpoint(getDefaultService(), policy.GET, "/path1"), 86 | Actions: getDefaultPathPolicy(), 87 | }, 88 | }, 89 | }, 90 | }, 91 | { 92 | name: "prefix path test", 93 | targets: []v1.TargetElement{ 94 | getTargetElements(service, getPathConfigs(getPathConfig("", "/", "", getDefaultPathPolicy()))), 95 | getTargetElements(service, getPathConfigs(getPathConfig("", "/path", "GET", getDefaultPathPolicy()))), 96 | getTargetElements(service, getPathConfigs(getPathConfig("", "/path1/", "GET", getDefaultPathPolicy()))), 97 | }, 98 | output: output{ 99 | total: 3, 100 | policies: []policy.PolicyMapping{ 101 | { 102 | Endpoint: getEndpoint(getDefaultService(), policy.ALL, "/*"), 103 | Actions: getDefaultPathPolicy(), 104 | }, 105 | { 106 | Endpoint: getEndpoint(getDefaultService(), policy.GET, "/path/*"), 107 | Actions: getDefaultPathPolicy(), 108 | }, 109 | { 110 | Endpoint: getEndpoint(getDefaultService(), policy.GET, "/path1/*"), 111 | Actions: getDefaultPathPolicy(), 112 | }, 113 | }, 114 | }, 115 | }, 116 | } 117 | 118 | for _, test := range tests { 119 | test := test 120 | t.Run(test.name, func(st *testing.T) { 121 | st.Parallel() 122 | result := ParseTarget(test.targets, ns) 123 | assert.Equal(t, len(result), test.output.total) 124 | if !reflect.DeepEqual(result, test.output.policies) { 125 | assert.Fail(t, fmt.Sprintf("expected out to have value %v, got %v", test.output.policies, result)) 126 | } 127 | }) 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /adapter/policy/handler/handler.go: -------------------------------------------------------------------------------- 1 | // Package handler is responsible for monitoring and maintaining authn/z policies 2 | package handler 3 | 4 | import ( 5 | "go.uber.org/zap" 6 | "k8s.io/client-go/kubernetes" 7 | 8 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 9 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/handler/crdeventhandler" 10 | policystore "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/store/policy" 11 | ) 12 | 13 | // PolicyHandler is responsible for storing and managing policy/client data 14 | type PolicyHandler interface { 15 | HandleAddUpdateEvent(obj interface{}) 16 | HandleDeleteEvent(obj interface{}) 17 | } 18 | 19 | // CrdHandler is responsible for storing and managing policy/client data 20 | type CrdHandler struct { 21 | store policystore.PolicyStore 22 | kubeClient kubernetes.Interface 23 | } 24 | 25 | // //////////////// constructor ////////////////// 26 | 27 | // New creates a PolicyManager 28 | func New(store policystore.PolicyStore, kubeClient kubernetes.Interface) PolicyHandler { 29 | return &CrdHandler{ 30 | store: store, 31 | kubeClient: kubeClient, 32 | } 33 | } 34 | 35 | // //////////////// interface ////////////////// 36 | 37 | // HandleAddUpdateEvent updates the store after a CRD has been added 38 | func (c *CrdHandler) HandleAddUpdateEvent(obj interface{}) { 39 | crdhandler := crdeventhandler.GetAddEventHandler(obj, c.store, c.kubeClient) 40 | if crdhandler != nil { 41 | crdhandler.HandleAddUpdateEvent() 42 | } 43 | } 44 | 45 | // HandleDeleteEvent updates the store after a CRD has been deleted 46 | func (c *CrdHandler) HandleDeleteEvent(obj interface{}) { 47 | crdKey, ok := obj.(policy.CrdKey) 48 | if !ok { 49 | zap.L().Warn("Expected to receive CrdKey from Kubernetes informer") 50 | return 51 | } 52 | zap.S().Debugf("crdKey : %s", crdKey.Id) 53 | zap.S().Debugf("crdType : %s", crdKey.CrdType) 54 | handler := crdeventhandler.GetDeleteEventHandler(crdKey, c.store) 55 | if handler != nil { 56 | handler.HandleDeleteEvent() 57 | } 58 | } -------------------------------------------------------------------------------- /adapter/policy/handler/handler_test.go: -------------------------------------------------------------------------------- 1 | package handler 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 8 | "k8s.io/client-go/kubernetes/fake" 9 | 10 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 11 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 12 | storePolicy "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/store/policy" 13 | ) 14 | 15 | const ( 16 | jwksUrl string = "https://sampleurl" 17 | ns string = "ns" 18 | ) 19 | 20 | func getObjectMeta() metav1.ObjectMeta { 21 | return metav1.ObjectMeta{Namespace: ns, Name: "sample"} 22 | } 23 | 24 | func getTypeMeta() metav1.TypeMeta { 25 | return metav1.TypeMeta{APIVersion: "v1", Kind: "OidcConfig"} 26 | } 27 | 28 | 29 | func getJwtConfigSpec(jwks string) v1.JwtConfigSpec { 30 | return v1.JwtConfigSpec{JwksURL: jwks} 31 | } 32 | 33 | func getJwtConfig(spec v1.JwtConfigSpec, objMeta metav1.ObjectMeta, typeMeta metav1.TypeMeta) *v1.JwtConfig { 34 | return &v1.JwtConfig{Spec: spec, ObjectMeta: objMeta, TypeMeta: typeMeta} 35 | } 36 | 37 | func jwtConfigGenerator() *v1.JwtConfig { 38 | jwtPolicySpec := getJwtConfigSpec(jwksUrl) 39 | return getJwtConfig(jwtPolicySpec, getObjectMeta(), getTypeMeta()) 40 | } 41 | 42 | func TestNew(t *testing.T) { 43 | assert.NotNil(t, New(storePolicy.New(), fake.NewSimpleClientset())) 44 | } 45 | 46 | func TestHandler_HandleEventTest(t *testing.T) { 47 | testHandler := &CrdHandler{ 48 | store: storePolicy.New(), 49 | } 50 | testHandler.HandleAddUpdateEvent(jwtConfigGenerator()) // Add 51 | testHandler.HandleAddUpdateEvent(jwtConfigGenerator()) // Update policy 52 | key := "ns/sample" 53 | assert.Equal(t, testHandler.store.GetKeySet(key).PublicKeyURL(), jwksUrl) 54 | testHandler.HandleDeleteEvent(policy.CrdKey{ 55 | Id: key, 56 | CrdType: v1.JWTCONFIG, 57 | }) 58 | assert.Nil(t, testHandler.store.GetKeySet(key)) 59 | } -------------------------------------------------------------------------------- /adapter/policy/policy.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | import ( 4 | "go.uber.org/zap" 5 | 6 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 7 | ) 8 | 9 | // Type represents a policy types (WEB/API) 10 | type Type int 11 | 12 | const ( 13 | // JWT policy types specifies requests protected by API strategy 14 | JWT Type = iota 15 | // OIDC policy types specifies requests protected by WEB strategy 16 | OIDC 17 | // NONE policy specifies requests without protection 18 | NONE 19 | ) 20 | 21 | // Endpoint captures a request endpoint 22 | type Endpoint struct { 23 | Service Service 24 | // Path holds a url path string 25 | Path string 26 | // Method holds an HTTP Method 27 | Method Method 28 | } 29 | 30 | // CrdKey represents a CustomResourceDefinition ID 31 | type CrdKey struct { 32 | Id string 33 | CrdType v1.CrdType 34 | } 35 | 36 | // PolicyMapping captures information of created endpoints by policy 37 | type PolicyMapping struct { 38 | Actions []v1.PathPolicy 39 | Endpoint Endpoint 40 | } 41 | 42 | type RoutePolicy struct { 43 | PolicyReference string 44 | Actions []v1.PathPolicy 45 | } 46 | 47 | func NewRoutePolicy() RoutePolicy { 48 | return RoutePolicy{ 49 | PolicyReference: "", 50 | Actions: make([]v1.PathPolicy, 0), 51 | } 52 | } 53 | 54 | // New creates a new ParsedPolicies 55 | func NewPolicyMapping(service Endpoint, actions []v1.PathPolicy) PolicyMapping { 56 | return PolicyMapping{ 57 | Endpoint: service, 58 | Actions: actions, 59 | } 60 | } 61 | 62 | var typeNames = [...]string{"JWT", "OIDC", "NONE"} 63 | 64 | func (t Type) String() string { 65 | return typeNames[t] 66 | } 67 | 68 | func NewType(t string) Type { 69 | zap.S().Info("Type: ", t) 70 | switch t { 71 | case "jwt": 72 | return JWT 73 | case "oidc": 74 | return OIDC 75 | default: 76 | return NONE 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /adapter/policy/service.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | // Service identifies a Kubernetes service 4 | type Service struct { 5 | // Namespace is the group the service lives in 6 | Namespace string 7 | // Name is the name of the service 8 | Name string 9 | } 10 | -------------------------------------------------------------------------------- /adapter/policy/store/pathtrie/pathtrie.go: -------------------------------------------------------------------------------- 1 | package pathtrie 2 | 3 | type PathTrie struct { 4 | segmenter StringSegmenter 5 | value interface{} 6 | children map[string]*PathTrie 7 | } 8 | 9 | func PathTrieNode() *PathTrie { 10 | return &PathTrie{ 11 | segmenter: PathSegmenter, 12 | children: make(map[string]*PathTrie), 13 | } 14 | } 15 | 16 | // NewPathTrie allocates and returns a new *PathTrie. 17 | func NewPathTrie() *PathTrie { 18 | trie := PathTrieNode() 19 | trie.Put("/", nil) 20 | return trie 21 | } 22 | 23 | // Get returns the value stored at the given key. Returns nil for internal 24 | // nodes or for nodes with a value of nil. 25 | func (trie *PathTrie) Get(key string) interface{} { 26 | node := trie 27 | for part, i := trie.segmenter(key, 0); ; part, i = trie.segmenter(key, i) { 28 | node = node.children[part] 29 | if node == nil { 30 | return nil 31 | } 32 | if i == -1 { 33 | break 34 | } 35 | } 36 | return node.value 37 | } 38 | 39 | // Get returns the actions stored for the given endpoint. 40 | func (trie *PathTrie) GetActions(key string) interface{} { 41 | prefix := "/*" 42 | node := trie 43 | parent := trie.children[prefix] 44 | for part, i := trie.segmenter(key, 0); ; part, i = trie.segmenter(key, i) { 45 | node = node.children[part] 46 | if node != nil && node.children[prefix] != nil { 47 | parent = node.children[prefix] 48 | } 49 | if node == nil || i == -1 { 50 | break 51 | } 52 | } 53 | 54 | if node == nil || node.value == nil { 55 | if parent == nil { 56 | return nil 57 | } else { 58 | return parent.value 59 | } 60 | } 61 | return node.value 62 | } 63 | 64 | // Put inserts the value into the trie at the given key, replacing any 65 | // existing items. It returns true if the put adds a new value, false 66 | // if it replaces an existing value. 67 | // Note that internal nodes have nil values so a stored nil value will not 68 | // be distinguishable and will not be included in Walks. 69 | func (trie *PathTrie) Put(key string, value interface{}) bool { 70 | node := trie 71 | for part, i := trie.segmenter(key, 0); ; part, i = trie.segmenter(key, i) { 72 | child, _ := node.children[part] 73 | if child == nil { 74 | child = PathTrieNode() 75 | node.children[part] = child 76 | } 77 | node = child 78 | if i == -1 { 79 | break 80 | } 81 | } 82 | // does node have an existing value? 83 | isNewVal := node.value == nil 84 | node.value = value 85 | return isNewVal 86 | } 87 | 88 | // Delete removes the value associated with the given key. Returns true if a 89 | // node was found for the given key. If the node or any of its ancestors 90 | // becomes childless as a result, it is removed from the trie. 91 | func (trie *PathTrie) Delete(key string) bool { 92 | var path []nodeStr // record ancestors to check later 93 | node := trie 94 | for part, i := trie.segmenter(key, 0); ; part, i = trie.segmenter(key, i) { 95 | path = append(path, nodeStr{part: part, node: node}) 96 | node = node.children[part] 97 | if node == nil { 98 | // node does not exist 99 | return false 100 | } 101 | if i == -1 { 102 | break 103 | } 104 | } 105 | // delete the node value 106 | node.value = nil 107 | // if leaf, remove it from its parent's children map. Repeat for ancestor path. 108 | if node.isLeaf() { 109 | // iterate backwards over path 110 | for i := len(path) - 1; i >= 0; i-- { 111 | parent := path[i].node 112 | part := path[i].part 113 | delete(parent.children, part) 114 | if parent.value != nil || !parent.isLeaf() { 115 | // parent has a value or has other children, stop 116 | break 117 | } 118 | } 119 | } 120 | return true // node (internal or not) existed and its value was nil'd 121 | } 122 | 123 | // PathTrie node and the part string key of the child the path descends into. 124 | type nodeStr struct { 125 | node *PathTrie 126 | part string 127 | } 128 | 129 | func (trie *PathTrie) isLeaf() bool { 130 | return len(trie.children) == 0 131 | } -------------------------------------------------------------------------------- /adapter/policy/store/pathtrie/pathtrie_test.go: -------------------------------------------------------------------------------- 1 | package pathtrie 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | type Case struct { 11 | key string 12 | value interface{} 13 | } 14 | 15 | func getCases() []Case { 16 | return []Case{ 17 | {"/path", 2}, 18 | {"/path/*", 3}, 19 | {"/path/path1", 4}, 20 | {"/home", 5}, 21 | {"/web", 6}, 22 | {"/web/*", 7}, 23 | } 24 | } 25 | 26 | func TestPathTrie(t *testing.T) { 27 | trie := NewPathTrie() 28 | value := 100 29 | cases := getCases() 30 | cases = append(cases, Case{"/", 0}, Case{"/*", 1}) 31 | // get missing keys 32 | for _, c := range cases { 33 | if value := trie.Get(c.key); value != nil { 34 | t.Errorf("expected key %s to be missing, found value %v", c.key, value) 35 | } 36 | } 37 | 38 | // initial put 39 | for _, c := range cases { 40 | if isNew := trie.Put(c.key, value); !isNew { 41 | t.Errorf("expected key %s to be missing", c.key) 42 | } 43 | } 44 | 45 | // subsequent put 46 | for _, c := range cases { 47 | if isNew := trie.Put(c.key, c.value); isNew { 48 | t.Errorf("expected key %s to have a value already", c.key) 49 | } 50 | } 51 | 52 | // get 53 | for _, c := range cases { 54 | if value := trie.Get(c.key); value != c.value { 55 | t.Errorf("expected key %s to have value %v, got %v", c.key, c.value, value) 56 | } 57 | } 58 | 59 | // delete, expect Delete to return true indicating a node was nil'd 60 | for _, c := range cases { 61 | if deleted := trie.Delete(c.key); !deleted { 62 | t.Errorf("expected key %s to be deleted", c.key) 63 | } 64 | } 65 | 66 | // delete cleaned all the way to the first character 67 | // expect Delete to return false bc no node existed to nil 68 | for _, c := range cases { 69 | if deleted := trie.Delete(string(c.key[0])); deleted { 70 | t.Errorf("expected key %s to be cleaned by delete", string(c.key[0])) 71 | } 72 | } 73 | 74 | // get deleted keys 75 | for _, c := range cases { 76 | if value := trie.Get(c.key); value != nil { 77 | t.Errorf("expected key %s to be deleted, got value %v", c.key, value) 78 | } 79 | } 80 | 81 | } 82 | 83 | func TestPathTrie_GetActions(t *testing.T) { 84 | trie := NewPathTrie() 85 | initialValues := getCases() 86 | 87 | // initial put 88 | for _, c := range initialValues { 89 | if isNew := trie.Put(c.key, c.value); !isNew { 90 | assert.Fail(t, fmt.Sprintf("expected key %s to be missing", c.key)) 91 | } 92 | } 93 | 94 | cases := getCases() 95 | cases = append(cases, Case{"/home/user", nil}, Case{"/path/home", 3}, Case{"/web/home", 7}) 96 | // Get Actions 97 | for _, c := range cases { 98 | if value := trie.GetActions(c.key); value != c.value { 99 | assert.Fail(t, fmt.Sprintf("expected key %s to have value %v, got %v", c.key, c.value, value)) 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /adapter/policy/store/pathtrie/trie.go: -------------------------------------------------------------------------------- 1 | package pathtrie 2 | 3 | // Exposes the Trie structure capabilities. 4 | type Trie interface { 5 | Get(key string) interface{} 6 | GetActions(key string) interface{} 7 | Put(key string, value interface{}) bool 8 | Delete(key string) bool 9 | } 10 | -------------------------------------------------------------------------------- /adapter/policy/store/pathtrie/utils.go: -------------------------------------------------------------------------------- 1 | package pathtrie 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // StringSegmenter takes a string key with a starting index and returns 8 | // the first segment after the start and the ending index. When the end is 9 | // reached, the returned nextIndex should be -1. 10 | // Implementations should NOT allocate heap memory as Trie Segmenters are 11 | // called upon Gets 12 | type StringSegmenter func(key string, start int) (segment string, nextIndex int) 13 | 14 | // PathSegmenter segments string key paths by slash separators. For example, 15 | // "/a/b/c" -> ("/a", 2), ("/b", 4), ("/c", -1) in successive calls. It does 16 | // not allocate any heap memory. 17 | func PathSegmenter(path string, start int) (segment string, next int) { 18 | if len(path) == 0 || start < 0 || start > len(path)-1 { 19 | return "", -1 20 | } 21 | end := strings.IndexRune(path[start+1:], '/') // next '/' after 0th rune 22 | if end == -1 { 23 | return path[start:], -1 24 | } 25 | return path[start : start+end+1], start + end + 1 26 | } 27 | -------------------------------------------------------------------------------- /adapter/policy/store/pathtrie/utils_test.go: -------------------------------------------------------------------------------- 1 | package pathtrie 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | 10 | func TestPathSegmenter(t *testing.T) { 11 | type result struct { 12 | segment string 13 | next int 14 | } 15 | type testObj struct { 16 | path string 17 | expected result 18 | } 19 | 20 | tests := [] testObj{ 21 | { 22 | path: "", 23 | expected: result{ 24 | segment: "", 25 | next: -1, 26 | }, 27 | }, 28 | { 29 | path: "/", 30 | expected: result{ 31 | segment: "/", 32 | next: -1, 33 | }, 34 | }, 35 | { 36 | path: "/*", 37 | expected: result{ 38 | segment: "/*", 39 | next: -1, 40 | }, 41 | }, 42 | { 43 | path: "/path", 44 | expected: result{ 45 | segment: "/path", 46 | next: -1, 47 | }, 48 | }, 49 | { 50 | path: "/path/user", 51 | expected: result{ 52 | segment: "/path", 53 | next: 5, 54 | }, 55 | }, 56 | } 57 | 58 | testRunner := func(test testObj) { 59 | t.Run("PathSegmenter", func(t *testing.T) { 60 | t.Parallel() 61 | segment, next := PathSegmenter(test.path, 0) 62 | assert.Equal(t, test.expected.segment, segment) 63 | assert.Equal(t, test.expected.next, next) 64 | }) 65 | } 66 | 67 | for _, test := range tests { 68 | testRunner(test) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /adapter/policy/store/policy/localstore.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | import ( 4 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/authserver/keyset" 5 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/client" 6 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 7 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/store/pathtrie" 8 | ) 9 | 10 | // LocalStore is responsible for storing and managing policy/client data 11 | type LocalStore struct { 12 | // clients maps client_name -> client_config 13 | clients map[string]client.Client // oidc config ClientName:Client 14 | // policies maps endpoint -> list of actions 15 | policies map[policy.Service]pathtrie.Trie 16 | // policyMappings maps policy(namespace/name) -> list of created endpoints 17 | policyMappings map[string][]policy.PolicyMapping 18 | keysets map[string]keyset.KeySet // jwt config ClientName:keyset 19 | } 20 | 21 | // New creates a new local store 22 | func New() PolicyStore { 23 | return &LocalStore{ 24 | clients: make(map[string]client.Client), 25 | policies: make(map[policy.Service]pathtrie.Trie), 26 | policyMappings: make(map[string][]policy.PolicyMapping), 27 | keysets: make(map[string]keyset.KeySet), 28 | } 29 | } 30 | 31 | func (l *LocalStore) GetKeySet(clientName string) keyset.KeySet { 32 | if l.keysets != nil { 33 | return l.keysets[clientName] 34 | } 35 | return nil 36 | } 37 | 38 | func (l *LocalStore) AddKeySet(clientName string, jwks keyset.KeySet) { 39 | if l.keysets == nil { 40 | l.keysets = make(map[string]keyset.KeySet) 41 | } 42 | l.keysets[clientName] = jwks 43 | } 44 | 45 | func (l *LocalStore) DeleteKeySet(clientName string) { 46 | if l.keysets != nil { 47 | delete(l.keysets, clientName) 48 | } 49 | } 50 | 51 | 52 | func (l *LocalStore) GetClient(clientName string) client.Client { 53 | if l.clients != nil { 54 | return l.clients[clientName] 55 | } 56 | return nil 57 | } 58 | 59 | func (l *LocalStore) AddClient(clientName string, clientObject client.Client) { 60 | if l.clients == nil { 61 | l.clients = make(map[string]client.Client) 62 | } 63 | l.clients[clientName] = clientObject 64 | } 65 | 66 | func (l *LocalStore) DeleteClient(clientName string) { 67 | if l.clients != nil { 68 | delete(l.clients, clientName) 69 | } 70 | } 71 | 72 | func (l *LocalStore) GetPolicies(endpoint policy.Endpoint) policy.RoutePolicy { 73 | if l.policies != nil && l.policies[endpoint.Service] != nil { 74 | actions, ok := (l.policies[endpoint.Service].GetActions(endpoint.Path)).(policy.Actions) 75 | if ok { 76 | result, present := actions[endpoint.Method] 77 | if present { // found actions for method 78 | return result 79 | } 80 | result, present = actions[policy.ALL] 81 | if present { // check if actions are set for ALL 82 | return result 83 | } 84 | } 85 | } 86 | return policy.NewRoutePolicy() 87 | } 88 | 89 | func (s *LocalStore) SetPolicies(endpoint policy.Endpoint, actions policy.RoutePolicy) { 90 | if s.policies == nil { 91 | s.policies = make(map[policy.Service]pathtrie.Trie) 92 | } 93 | if s.policies[endpoint.Service] == nil { 94 | s.policies[endpoint.Service] = pathtrie.NewPathTrie() 95 | } 96 | 97 | if obj, ok := (s.policies[endpoint.Service].GetActions(endpoint.Path)).(policy.Actions); ok { 98 | obj[endpoint.Method] = actions 99 | } else { 100 | obj := policy.NewActions() 101 | obj[endpoint.Method] = actions 102 | s.policies[endpoint.Service].Put(endpoint.Path, obj) 103 | } 104 | } 105 | 106 | func (s *LocalStore) GetPolicyMapping(policy string) []policy.PolicyMapping { 107 | if s.policyMappings != nil { 108 | return s.policyMappings[policy] 109 | } 110 | return nil 111 | } 112 | 113 | func (s *LocalStore) DeletePolicyMapping(policy string) { 114 | if s.policyMappings != nil { 115 | delete(s.policyMappings, policy) 116 | } 117 | } 118 | 119 | func (s *LocalStore) AddPolicyMapping(name string, mapping []policy.PolicyMapping) { 120 | if s.policyMappings == nil { 121 | s.policyMappings = make(map[string][]policy.PolicyMapping) 122 | } 123 | s.policyMappings[name] = mapping 124 | } 125 | -------------------------------------------------------------------------------- /adapter/policy/store/policy/localstore_test.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | v1 "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis/policies/v1" 9 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 10 | 11 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/fake" 12 | ) 13 | 14 | const ( 15 | clientname = "clientname" 16 | jwksurl = "http://mockserver" 17 | endpoint = "service/path" 18 | samplePolicy = "policy" 19 | ) 20 | 21 | func getService() policy.Service { 22 | return policy.Service{ 23 | Name: "sample", 24 | Namespace: "ns", 25 | } 26 | } 27 | 28 | func getActions() policy.Actions { 29 | actions := policy.NewActions() 30 | actions[policy.GET] = policy.RoutePolicy { 31 | Actions: []v1.PathPolicy{ 32 | {PolicyType: "jwt", Config:"samplejwt"}, 33 | }, 34 | } 35 | actions[policy.ALL] = policy.RoutePolicy { 36 | Actions: []v1.PathPolicy{ 37 | {PolicyType:"oidc", Config:"sampleoidc", RedirectUri:"https://sampleapp.com"}, 38 | }, 39 | } 40 | return actions 41 | } 42 | 43 | func getEndpoint(service policy.Service, path string, method policy.Method) policy.Endpoint { 44 | return policy.Endpoint{ 45 | Service: service, 46 | Path: path, 47 | Method: method, 48 | } 49 | } 50 | 51 | func TestNew(t *testing.T) { 52 | assert.NotNil(t, New()) 53 | } 54 | 55 | func keySetTest(t *testing.T, store PolicyStore) { 56 | assert.Nil(t, store.GetKeySet(jwksurl)) 57 | store.AddKeySet(jwksurl, &fake.KeySet{}) 58 | assert.NotNil(t, store.GetKeySet(jwksurl)) 59 | } 60 | func TestLocalStore_KeySet(t *testing.T) { 61 | keySetTest(t, &LocalStore{}) 62 | keySetTest(t, New()) 63 | } 64 | 65 | func clientTest(t *testing.T, store PolicyStore) { 66 | assert.Nil(t, store.GetClient(clientname)) 67 | store.AddClient(clientname, &fake.Client{}) 68 | assert.NotNil(t, store.GetClient(clientname)) 69 | } 70 | func TestLocalStore_Client(t *testing.T) { 71 | clientTest(t, &LocalStore{}) 72 | clientTest(t, New()) 73 | } 74 | 75 | func policiesTest(t *testing.T, store PolicyStore) { 76 | assert.Equal(t, store.GetPolicies(getEndpoint(getService(), endpoint, policy.GET)), policy.NewRoutePolicy()) 77 | store.SetPolicies(getEndpoint(getService(), endpoint, policy.ALL), 78 | policy.RoutePolicy{ Actions:[]v1.PathPolicy{ {PolicyType:"oidc", Config:"sampleoidc", RedirectUri:"https://sampleapp.com"}}}) 79 | store.SetPolicies(getEndpoint(getService(), endpoint, policy.GET), 80 | policy.RoutePolicy{Actions: []v1.PathPolicy{{PolicyType: "jwt", Config:"samplejwt"}}}) 81 | assert.Equal(t, store.GetPolicies(getEndpoint(getService(), endpoint, policy.GET)), getActions()[policy.GET]) 82 | assert.Equal(t, store.GetPolicies(getEndpoint(getService(), endpoint, policy.PUT)), getActions()[policy.ALL]) 83 | } 84 | 85 | func TestLocalStore_Policies(t *testing.T) { 86 | policiesTest(t, &LocalStore{}) 87 | policiesTest(t, New()) 88 | } 89 | 90 | func policyMappingTest(t *testing.T, store PolicyStore) { 91 | assert.Nil(t, store.GetPolicyMapping(samplePolicy)) 92 | store.AddPolicyMapping(samplePolicy, []policy.PolicyMapping{}) 93 | assert.NotNil(t, store.GetPolicyMapping(samplePolicy)) 94 | store.DeletePolicyMapping(samplePolicy) 95 | assert.Nil(t, store.GetPolicyMapping(samplePolicy)) 96 | } 97 | func TestLocalStore_PolicyMapping(t *testing.T) { 98 | policyMappingTest(t, &LocalStore{}) 99 | policyMappingTest(t, New()) 100 | } 101 | -------------------------------------------------------------------------------- /adapter/policy/store/policy/policystore.go: -------------------------------------------------------------------------------- 1 | package policy 2 | 3 | import ( 4 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/authserver/keyset" 5 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/client" 6 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy" 7 | ) 8 | 9 | // PolicyStore stores policy information 10 | type PolicyStore interface { 11 | GetKeySet(clientName string) keyset.KeySet 12 | AddKeySet(clientName string, jwks keyset.KeySet) 13 | DeleteKeySet(clientName string) 14 | GetClient(clientName string) client.Client 15 | AddClient(clientName string, client client.Client) 16 | DeleteClient(clientName string) 17 | GetPolicies(endpoint policy.Endpoint) policy.RoutePolicy 18 | SetPolicies(endpoint policy.Endpoint, actions policy.RoutePolicy) 19 | // DeletePolicies(ep policy.Endpoint, obj interface{}) 20 | GetPolicyMapping(policy string) []policy.PolicyMapping 21 | AddPolicyMapping(policy string, mapping []policy.PolicyMapping) 22 | DeletePolicyMapping(policy string) 23 | } 24 | -------------------------------------------------------------------------------- /adapter/strategy/strategy.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "fmt" 5 | 6 | policy "istio.io/api/policy/v1beta1" 7 | 8 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/policy/engine" 9 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/config/template" 10 | ) 11 | 12 | // Strategy defines the entry point to an authentication handler 13 | type Strategy interface { 14 | HandleAuthnZRequest(*authnz.HandleAuthnZRequest, *engine.Action) (*authnz.HandleAuthnZResponse, error) 15 | } 16 | 17 | // DecodeValueMap decodes gRPC values into string interface 18 | func DecodeValueMap(in map[string]*policy.Value) map[string]interface{} { 19 | out := make(map[string]interface{}, len(in)) 20 | for k, v := range in { 21 | out[k] = decodeValue(v.GetValue()) 22 | } 23 | return out 24 | } 25 | 26 | // DecodeValue decodes policy value into standard type 27 | func decodeValue(in interface{}) interface{} { 28 | switch t := in.(type) { 29 | case *policy.Value_StringValue: 30 | return t.StringValue 31 | case *policy.Value_Int64Value: 32 | return t.Int64Value 33 | case *policy.Value_DoubleValue: 34 | return t.DoubleValue 35 | case *policy.Value_IpAddressValue: 36 | return t.IpAddressValue 37 | default: 38 | return fmt.Sprintf("%v", in) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /adapter/strategy/strategy_test.go: -------------------------------------------------------------------------------- 1 | package strategy 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "istio.io/api/policy/v1beta1" 8 | ) 9 | 10 | func TestDecodeValueMap(t *testing.T) { 11 | m := generateMap() 12 | strInterfaceMap := DecodeValueMap(m) 13 | assert.Equal(t, "Value_StringValue", strInterfaceMap["Value_StringValue"]) 14 | assert.Equal(t, int64(1), strInterfaceMap["Value_Int64Value"]) 15 | assert.Equal(t, float64(10), strInterfaceMap["Value_DoubleValue"]) 16 | assert.NotNil(t, strInterfaceMap["Value_IpAddressValue"]) 17 | assert.Equal(t, "&Value_BoolValue{BoolValue:true,}", strInterfaceMap["Value_BoolValue"]) 18 | 19 | } 20 | 21 | func generateMap() map[string]*v1beta1.Value { 22 | return map[string]*v1beta1.Value{ 23 | "Value_BoolValue": { 24 | Value: &v1beta1.Value_BoolValue{ 25 | BoolValue: true, 26 | }, 27 | }, 28 | "Value_StringValue": { 29 | Value: &v1beta1.Value_StringValue{ 30 | StringValue: "Value_StringValue", 31 | }, 32 | }, 33 | "Value_Int64Value": { 34 | Value: &v1beta1.Value_Int64Value{ 35 | Int64Value: int64(1), 36 | }, 37 | }, 38 | "Value_DoubleValue": { 39 | Value: &v1beta1.Value_DoubleValue{ 40 | DoubleValue: 10, 41 | }, 42 | }, 43 | "Value_IpAddressValue": { 44 | Value: &v1beta1.Value_IpAddressValue{ 45 | IpAddressValue: &v1beta1.IPAddress{ 46 | Value: make([]byte, 0), 47 | }, 48 | }, 49 | }, 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /adapter/strategy/web/utils.go: -------------------------------------------------------------------------------- 1 | package webstrategy 2 | 3 | import ( 4 | "errors" 5 | "math/rand" 6 | "net/http" 7 | "net/url" 8 | "time" 9 | "unsafe" 10 | 11 | "go.uber.org/zap" 12 | 13 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/client" 14 | authnz "github.com/ibm-cloud-security/app-identity-and-access-adapter/config/template" 15 | ) 16 | 17 | const ( 18 | letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 19 | letterIdxBits = 6 // 6 bits to represent a letter index 20 | letterIdxMask = 1<= 0; { 31 | if remain == 0 { 32 | cache, remain = src.Int63(), letterIdxMax 33 | } 34 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) { 35 | b[i] = letterBytes[idx] 36 | i-- 37 | } 38 | cache >>= letterIdxBits 39 | remain-- 40 | } 41 | 42 | return *(*string)(unsafe.Pointer(&b)) 43 | } 44 | 45 | // generateAuthorizationURL builds the /authorization request that begins an OAuth 2.0 / OIDC flow 46 | func generateAuthorizationURL(c client.Client, redirectURI string, state string) string { 47 | server := c.AuthorizationServer() 48 | if server == nil { 49 | zap.L().Warn("Authorization server has not been configured for client", zap.Error(errors.New("authorization server has not been configured")), zap.String("client_name", c.Name())) 50 | return "" 51 | } 52 | baseUrl, err := url.Parse(server.AuthorizationEndpoint()) 53 | if err != nil { 54 | zap.L().Warn("Malformed Authorization URL", zap.Error(err)) 55 | return "" 56 | } 57 | 58 | // Prepare Query Parameters 59 | params := url.Values{ 60 | "client_id": {c.ID()}, 61 | "response_type": {"code"}, 62 | "redirect_uri": {redirectURI}, 63 | "scope": {c.Scope()}, 64 | "state": {state}, 65 | } 66 | 67 | // Add Query Parameters to the URL 68 | baseUrl.RawQuery = params.Encode() // Escape Query Parameters 69 | 70 | return baseUrl.String() 71 | } 72 | 73 | // buildRequestURL constructs the original url from the request object 74 | func buildRequestURL(action *authnz.RequestMsg) string { 75 | return action.Scheme + "://" + action.Host + action.Path 76 | } 77 | 78 | // buildTokenCookieName constructs the cookie name 79 | func buildTokenCookieName(base string, c client.Client) string { 80 | return base + "-" + c.ID() 81 | } 82 | 83 | // generateSessionIDCookie creates a new sessionId cookie 84 | // if the provided value is empty and new id is randomly generated 85 | func generateSessionIDCookie(c client.Client, value *string) *http.Cookie { 86 | var v = randString(15) 87 | if value != nil { 88 | v = *value 89 | } 90 | return &http.Cookie{ 91 | Name: buildTokenCookieName(sessionCookie, c), 92 | Value: v, 93 | Path: "/", 94 | Secure: false, // TODO: replace on release 95 | HttpOnly: false, 96 | Expires: time.Now().Add(time.Hour * time.Duration(2160)), // 90 days 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /adapter/strategy/web/utils_test.go: -------------------------------------------------------------------------------- 1 | package webstrategy 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | 8 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/client" 9 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/config/template" 10 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/fake" 11 | ) 12 | 13 | func TestRandString(t *testing.T) { 14 | str1 := randString(10) 15 | str2 := randString(10) 16 | str3 := randString(4) 17 | assert.Equal(t, 10, len(str1)) 18 | assert.Equal(t, 10, len(str2)) 19 | assert.Equal(t, 4, len(str3)) 20 | assert.NotEqual(t, str1, str2) 21 | } 22 | 23 | func TestGenerateAuthorizationURL(t *testing.T) { 24 | type testObj struct { 25 | expected string 26 | redirectURI string 27 | state string 28 | c client.Client 29 | } 30 | tests := []testObj{ 31 | { 32 | c: &fake.Client{}, 33 | }, 34 | { 35 | expected: "https://auth.com/authorization?client_id=id&redirect_uri=https%3A%2F%2Fredirect.com&response_type=code&scope=openid+profile+email&state=12345", 36 | c: fake.NewClient(nil), 37 | redirectURI: "https://redirect.com", 38 | state: "12345", 39 | }, 40 | } 41 | 42 | testRunner := func(test testObj) { 43 | t.Run("Authorization URL", func(t *testing.T) { 44 | t.Parallel() 45 | url := generateAuthorizationURL(test.c, test.redirectURI, test.state) 46 | assert.Equal(t, test.expected, url) 47 | }) 48 | } 49 | 50 | for _, test := range tests { 51 | testRunner(test) 52 | } 53 | } 54 | 55 | func TestBuildRequestURL(t *testing.T) { 56 | inp := &authnz.RequestMsg{ 57 | Scheme: "https", 58 | Host: "me.com", 59 | Path: "/hello world", 60 | } 61 | assert.Equal(t, "https://me.com/hello world", buildRequestURL(inp)) 62 | } 63 | 64 | func TestTokenCookieName(t *testing.T) { 65 | assert.Equal(t, "base-string-id", buildTokenCookieName("base-string", fake.NewClient(nil))) 66 | } 67 | -------------------------------------------------------------------------------- /adapter/validator/token.go: -------------------------------------------------------------------------------- 1 | package validator 2 | 3 | type Token int 4 | 5 | const ( 6 | Access Token = iota 7 | ID 8 | ) 9 | 10 | func (t Token) String() string { 11 | return [...]string{"access_token", "id_token"}[t] 12 | } 13 | -------------------------------------------------------------------------------- /bin/build_deploy.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2019 APP ID Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | # Adapter information 19 | adapterName="appidentityandaccessadapter" 20 | sourceDir="$(dirname "${BASH_SOURCE[0]}")" 21 | sampleAppNamespace="sample-app" 22 | sampleAppDeploymentName="dpl-sample-app" 23 | sampleAppYaml=${sourceDir}/../samples/app/sample-app.yaml 24 | 25 | function checkEnv() { 26 | if [[ -z "$(command -v docker)" ]]; then 27 | echo "Could not find 'docker' in path" 28 | exit 1 29 | fi 30 | 31 | if [[ -z "$(command -v helm)" ]]; then 32 | echo "Could not find 'helm' in path" 33 | exit 1 34 | fi 35 | 36 | if [[ -z "$(command -v kubectl)" ]]; then 37 | echo "Could not find 'kubectl' in path" 38 | exit 1 39 | fi 40 | } 41 | 42 | function reportError() { 43 | if [[ $1 -ne 0 ]]; then 44 | echo $2 45 | exit $1 46 | fi 47 | } 48 | 49 | function buildAndTag() { 50 | echo "Building executable" 51 | bash -x ${sourceDir}/build_executable.sh 52 | reportError $? "job has failed, please check the log for details" 53 | 54 | echo "Building and deploying docker image" 55 | source ${sourceDir}/docker_build_tag_push.sh $1 56 | } 57 | 58 | function deleteAndWait() { 59 | echo "Cleaning up cluster" 60 | helm delete --purge ${adapterName} 61 | 62 | } 63 | 64 | function installAdapter() { 65 | echo "Installing adapter" 66 | helm install --wait helm/${adapterName} --name ${adapterName} --set image.tag=$1 67 | } 68 | 69 | function installDemoApplication() { 70 | if (kubectl wait --for=condition=available --timeout=600s deployment/${sampleAppDeploymentName} -n ${sampleAppNamespace}); then 71 | echo "Sample app has already been deployed" 72 | else 73 | echo "Deploying sample app" 74 | # TODO: Inject Pods 75 | #kubectl apply -f ${sampleAppYaml} 76 | #kubectl wait --for=condition=available --timeout=600s deployment/${sampleAppDeploymentName} -n ${sampleAppNamespace} 77 | fi 78 | } 79 | 80 | ### Execute 81 | checkEnv 82 | buildAndTag $1 83 | deleteAndWait 84 | installAdapter "${IMAGE_TEST_TAG}" 85 | installDemoApplication -------------------------------------------------------------------------------- /bin/build_executable.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2019 APP ID Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | echo "Building Linux Executable" 19 | sourceDir="$(dirname "${BASH_SOURCE[0]}")" 20 | 21 | # Remove old executable 22 | rm -f ${sourceDir}/appidentityandaccessadapter 23 | 24 | # Compile new executable 25 | env GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags="-s -w" -a -installsuffix cgo -v -o ${sourceDir}/appidentityandaccessadapter ${sourceDir}/../cmd/main.go 26 | -------------------------------------------------------------------------------- /bin/docker_build_tag_push.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2019 APP ID Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | function checkTools() { 19 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then 20 | echo "WARN: script NOT sourced and will not expose image tag" 21 | fi 22 | if [[ -z "$(command -v docker)" ]]; then 23 | echo "Could not find 'docker' in path" 24 | exit 1 25 | fi 26 | } 27 | 28 | function buildTag() { 29 | if [[ ! -z $1 ]]; then 30 | echo $1 31 | return 32 | fi 33 | if [[ -z ${TRAVIS+x} ]]; then 34 | echo $USER | cut -f1 -d"@" 35 | return 36 | fi 37 | if [[ $TRAVIS_PULL_REQUEST != "false" ]]; then 38 | echo pr-${TRAVIS_PULL_REQUEST_BRANCH} 39 | return 40 | else 41 | echo branch-${TRAVIS_BRANCH} 42 | return 43 | fi 44 | } 45 | 46 | function buildAndDeploy() { 47 | 48 | if ! grep -q "https://index.docker.io/v1/" ~/.docker/config.json ; then 49 | echo "Not logged into Docker. Logging in using env credentials." 50 | echo ${DOCKER_PASSWORD} | docker login -u ${DOCKER_USERNAME} --password-stdin 51 | fi 52 | 53 | echo "Building Docker image: ${IMAGE_TAG}" 54 | docker build -t ${IMAGE_TAG} ${sourceDir}/../. 55 | 56 | echo "Pushing Docker image to container registry" 57 | docker push ${IMAGE_TAG} 58 | } 59 | 60 | 61 | IMAGE_REGISTRY_NAMESPACE=${IMAGE_REGISTRY_NAMESPACE:-ibmcloudsecurity} 62 | APP_NAME=${APP_NAME:-app-identity-and-access-adapter} 63 | TAG=$(buildTag $1) 64 | IMAGE_TAG=${IMAGE_REGISTRY_NAMESPACE}/${APP_NAME}:${TAG} 65 | sourceDir="$(dirname "${BASH_SOURCE[0]}")" 66 | 67 | # Execute 68 | checkTools 69 | buildAndDeploy 70 | export IMAGE_TEST_TAG=${TAG} 71 | -------------------------------------------------------------------------------- /bin/ibmcloud_login.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2019 APP ID Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | ## Cluster Information 19 | 20 | # Adapter information 21 | adapterName="appidentityandaccessadapter" 22 | sourceDir="$(dirname "${BASH_SOURCE[0]}")" 23 | 24 | 25 | function checkEnv() { 26 | if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then 27 | echo "error this script should be sourced" 28 | exit 4 29 | fi 30 | 31 | if [[ -z "$(command -v ibmcloud)" ]]; then 32 | echo "Could not find 'ibmcloud' in path" 33 | exit 1 34 | fi 35 | } 36 | 37 | function configureCluster() { 38 | echo "Logging into IBM Cloud." 39 | ibmcloud login -r ${REGION} --apikey ${IBM_CLOUD_API_KEY} 40 | 41 | 42 | ibmcloud ks cluster config --cluster ${CLUSTER_NAME} 43 | 44 | local homeDir="home" 45 | if [[ -z ${TRAVIS+x} ]]; then 46 | homeDir="Users" 47 | fi 48 | } 49 | 50 | # Execute 51 | checkEnv 52 | configureCluster 53 | -------------------------------------------------------------------------------- /bin/install_tools.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # Copyright 2019 APP ID Authors. All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | function installHelm() { 19 | wget https://storage.googleapis.com/kubernetes-helm/helm-v2.13.0-linux-amd64.tar.gz 20 | tar -xvzf helm-v2.13.0-linux-amd64.tar.gz 21 | sudo mv linux-amd64/helm /usr/local/bin/helm 22 | } 23 | 24 | function installIBMCloudCLI() { 25 | curl -fsSL https://clis.cloud.ibm.com/install/linux | sh 26 | } 27 | 28 | function installIBMCloudPlugins() { 29 | ibmcloud plugin install container-service 30 | } 31 | 32 | function installKubectl() { 33 | curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl 34 | chmod +x ./kubectl 35 | sudo mv ./kubectl /usr/local/bin/kubectl 36 | } 37 | 38 | installHelm 39 | installKubectl 40 | installIBMCloudCLI 41 | installIBMCloudPlugins 42 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | "go.uber.org/zap" 9 | "go.uber.org/zap/zapcore" 10 | 11 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter" 12 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/config" 13 | ) 14 | 15 | // GetCmd returns the cobra command-tree. 16 | func getCmd() *cobra.Command { 17 | sa := config.NewConfig() 18 | cmd := &cobra.Command{ 19 | Use: "Starts the App Identity and Access out-of-process Mixer adapter", 20 | Short: "Starts the App Identity and Access out-of-process Mixer adapter", 21 | Run: func(cmd *cobra.Command, args []string) { 22 | runServer(sa) 23 | }, 24 | PersistentPreRunE: func(cmd *cobra.Command, args []string) error { 25 | if len(args) > 0 { 26 | return fmt.Errorf("'%s' is an invalid argument", args[0]) 27 | } 28 | return nil 29 | }, 30 | } 31 | 32 | f := cmd.PersistentFlags() 33 | f.Uint16VarP(&sa.AdapterPort, "port", "p", sa.AdapterPort, "TCP port to use for gRPC Adapter API.") 34 | f.BoolVarP(&sa.Json, "json", "j", sa.Json, "Use JSON style logging.") 35 | f.Int8VarP(&sa.Level, "level", "l", sa.Level, "Set output log level. Range [-1, 7].") 36 | f.VarP(&sa.HashKeySize, "hash-key", "", "The size of the HMAC signature key. It is recommended to use a key with 32 or 64 bytes.") 37 | f.VarP(&sa.BlockKeySize, "block-key", "", "The size of the AES blockKey size used to encrypt the cookie value. Valid lengths are 16, 24, or 32.") 38 | 39 | return cmd 40 | } 41 | 42 | func configureLogger(args *config.Config) { 43 | config := zap.NewProductionConfig() 44 | config.Level = zap.NewAtomicLevelAt(zapcore.Level(args.Level)) 45 | config.InitialFields = map[string]interface{}{"source": "appidentityandaccessadapter-adapter"} 46 | config.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder 47 | if !args.Json { 48 | config.Encoding = "console" 49 | } 50 | logger, _ := config.Build() 51 | zap.ReplaceGlobals(logger) 52 | } 53 | 54 | func runServer(args *config.Config) { 55 | configureLogger(args) 56 | 57 | // Configure Adapter 58 | s, err := adapter.NewAppIDAdapter(args) 59 | if err != nil { 60 | zap.L().Fatal("Failed to create appidentityandaccessadapter.NewAppIDAdapter: %s", zap.Error(err)) 61 | } 62 | 63 | shutdown := make(chan error, 1) 64 | go func() { 65 | s.Run(shutdown) 66 | }() 67 | <-shutdown 68 | } 69 | 70 | func main() { 71 | cmd := getCmd() 72 | if err := cmd.Execute(); err != nil { 73 | os.Exit(-1) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /config/adapter/adapter.appidentityandaccessadapter.config.pb.html: -------------------------------------------------------------------------------- 1 | --- 2 | title: App Identity and Access 3 | description: Adapter to enforce authentication and authorization policies for web apps and APIs. 4 | location: https://istio.io/docs/reference/config/policy-and-telemetry/adapters/app-identity-access-adapter.html 5 | layout: protoc-gen-docs 6 | generator: protoc-gen-docs 7 | provider: IBM Cloud 8 | contact_email: antona@us.ibm.com 9 | source_link: https://github.com/ibm-cloud-security/app-identity-and-access-adapter 10 | latest_release_link: https://github.com/ibm-cloud-security/app-identity-and-access-adapter 11 | helm_chart_link: 12 | istio_versions: "1.1.x, 1.2.x" 13 | number_of_entries: 0 14 | --- 15 |

With the App Identity and Access Istio adapter, you can use any OIDC compliant identity provider, such as IBM Cloud App ID, to 16 | protect your APIs and containerized web apps. Without any change to your code or the need to redeploy 17 | your app, you can enforce authentication and authorization policies in all of your environments. 18 | To get started with sample configurations see the docs.

19 | -------------------------------------------------------------------------------- /config/adapter/config.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-gogo. DO NOT EDIT. 2 | // source: app-identity-and-access-adapter/config/adapter/config.proto 3 | 4 | // With the App Identity and Access Istio adapter, you can use any OIDC compliant identity provider, such as [IBM Cloud App ID](https://www.ibm.com/cloud/app-id), to 5 | // protect your APIs and containerized web apps. Without any change to your code or the need to redeploy 6 | // your app, you can enforce authentication and authorization policies in all of your environments. 7 | // To get started with sample configurations see the [docs] (https://cloud.ibm.com/docs/services/appid?topic=appid-istio-adapter). 8 | 9 | package config 10 | 11 | import ( 12 | fmt "fmt" 13 | _ "github.com/gogo/protobuf/gogoproto" 14 | proto "github.com/gogo/protobuf/proto" 15 | math "math" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var _ = proto.Marshal 20 | var _ = fmt.Errorf 21 | var _ = math.Inf 22 | 23 | // This is a compile-time assertion to ensure that this generated file 24 | // is compatible with the proto package it is being compiled against. 25 | // A compilation error at this line likely means your copy of the 26 | // proto package needs to be updated. 27 | const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package 28 | 29 | func init() { 30 | proto.RegisterFile("app-identity-and-access-adapter/config/adapter/config.proto", fileDescriptor_36b698f07148d97d) 31 | } 32 | 33 | var fileDescriptor_36b698f07148d97d = []byte{ 34 | // 171 bytes of a gzipped FileDescriptorProto 35 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xb2, 0x4e, 0x2c, 0x28, 0xd0, 36 | 0xcd, 0x4c, 0x49, 0xcd, 0x2b, 0xc9, 0x2c, 0xa9, 0xd4, 0x4d, 0xcc, 0x4b, 0xd1, 0x4d, 0x4c, 0x4e, 37 | 0x4e, 0x2d, 0x2e, 0xd6, 0x4d, 0x4c, 0x49, 0x2c, 0x28, 0x49, 0x2d, 0xd2, 0x4f, 0xce, 0xcf, 0x4b, 38 | 0xcb, 0x4c, 0xd7, 0x47, 0xe5, 0xea, 0x15, 0x14, 0xe5, 0x97, 0xe4, 0x0b, 0x69, 0x41, 0x45, 0xf5, 39 | 0x12, 0x0b, 0x0a, 0x60, 0x66, 0x24, 0xe6, 0xa5, 0x40, 0x4c, 0x80, 0xc9, 0x41, 0x74, 0x48, 0x89, 40 | 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0xb5, 0xe9, 0x83, 0x58, 0x10, 0x13, 0x9c, 0x6c, 0x2e, 0x3c, 0x94, 41 | 0x63, 0xb8, 0xf1, 0x50, 0x8e, 0xe1, 0xc3, 0x43, 0x39, 0xc6, 0x86, 0x47, 0x72, 0x8c, 0x2b, 0x1e, 42 | 0xc9, 0x31, 0x9e, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, 0x47, 0x72, 0x8c, 0x2f, 43 | 0x1e, 0xc9, 0x31, 0x7c, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, 44 | 0x37, 0x1e, 0xcb, 0x31, 0x44, 0xb1, 0x41, 0xcc, 0x4c, 0x62, 0x03, 0x1b, 0x62, 0x0c, 0x08, 0x00, 45 | 0x00, 0xff, 0xff, 0x98, 0xb2, 0x11, 0x96, 0xc5, 0x00, 0x00, 0x00, 46 | } 47 | -------------------------------------------------------------------------------- /config/adapter/config.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // $title: App Identity and Access 4 | // $description: Adapter to enforce authentication and authorization policies for web apps and APIs. 5 | // $location: https://istio.io/docs/reference/config/policy-and-telemetry/adapters/app-identity-access-adapter.html 6 | // $provider: IBM Cloud 7 | // $contact_email: antona@us.ibm.com 8 | // $source_link: https://github.com/ibm-cloud-security/app-identity-and-access-adapter 9 | // $latest_release_link: https://github.com/ibm-cloud-security/app-identity-and-access-adapter 10 | // $helm_chart_link: 11 | // $istio_versions: "1.1.x, 1.2.x" 12 | 13 | 14 | // With the App Identity and Access Istio adapter, you can use any OIDC compliant identity provider, such as [IBM Cloud App ID](https://www.ibm.com/cloud/app-id), to 15 | // protect your APIs and containerized web apps. Without any change to your code or the need to redeploy 16 | // your app, you can enforce authentication and authorization policies in all of your environments. 17 | // To get started with sample configurations see the [docs] (https://cloud.ibm.com/docs/services/appid?topic=appid-istio-adapter). 18 | package adapter.appidentityandaccessadapter.config; 19 | 20 | import "gogoproto/gogo.proto"; 21 | 22 | option go_package="config"; 23 | -------------------------------------------------------------------------------- /config/adapter/config.proto_descriptor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-cloud-security/app-identity-and-access-adapter/200b26a419a5bbe1c625e4aee7c3bdb0bf561909/config/adapter/config.proto_descriptor -------------------------------------------------------------------------------- /config/template/template.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | // Template authnZ defines an authorization and authentication adapter template 4 | package authnZ; 5 | 6 | import "policy/v1beta1/type.proto"; 7 | import "mixer/adapter/model/v1beta1/extensions.proto"; 8 | 9 | option (istio.mixer.adapter.model.v1beta1.template_variety) = TEMPLATE_VARIETY_CHECK_WITH_OUTPUT; 10 | 11 | // A Target contains the Action destination. 12 | message Target { 13 | // The namespace the target service is in 14 | string namespace = 1; 15 | // The service the action is being taken on. 16 | string service = 2; 17 | // The HTTP method of the request 18 | string method = 3; 19 | // The HTTP REST path within the service 20 | string path = 4; 21 | // Additional data about the target for use in policy. 22 | map properties = 5; 23 | } 24 | 25 | // The Headers models the core HTTP headers needed for the JWT/OIDC flows 26 | message Headers { 27 | // The optional cookies are the HTTP request cookies sent by the browser. These 28 | // contain the encrypted session toke 29 | string cookies = 1; 30 | // The optional authorization header contains credentials needed to verify 31 | // access / authorization privileges. 32 | string authorization = 2; 33 | // Additional data about the headers for use in policy. 34 | map properties = 3; 35 | } 36 | 37 | // The QueryParams are the code HTTP request query parameters used in an OAuth 2.0 / OIDC flow 38 | message QueryParams { 39 | // The error matches an OAuth 2.0 callback error response 40 | string error = 1; 41 | // The code matches an OAuth 2.0 callback authorization code grant 42 | string code = 2; 43 | // The state matches an OAuth 2.0 callback authorization state parameter 44 | string state = 3; 45 | // Additional data about the query parameters for use in policy. 46 | map properties = 4; 47 | } 48 | 49 | // The Request captures information about the incoming HTTP request 50 | message Request { 51 | // The HTTP scheme 52 | string scheme = 1; 53 | // The HTTP host 54 | string host = 2; 55 | // The HTTP path 56 | string path = 3; 57 | // The HTTP headers on the request 58 | Headers headers = 4; 59 | // THE HTTP query params 60 | QueryParams params = 5; 61 | // Additional data about the Request for use in policy. 62 | map properties = 6; 63 | } 64 | 65 | // The Authn/Z template contains the information necessary to 66 | // control authorization and authentication using OAuth 2.0 / OIDC 67 | // defined protocols. It captures information about the request source, destination 68 | // and code request telemetry that allows comprehensive JWT policy definitions. 69 | message Template { 70 | // The request contains the core information about the request being made 71 | Request request = 1; 72 | // The target contains aggregated Kube information about the destination 73 | Target target = 2; 74 | } 75 | 76 | // The Authn/Z template produces an authorization header of the format 77 | // `Bearer ` which should be replaced on the ongoing request. 78 | // as well as optional cookies to be set on the outgoing response 79 | message OutputTemplate { 80 | // The authorization header 81 | string authorization = 1; 82 | // The session-cookie to append to the response 83 | string sessionCookie = 2; 84 | } -------------------------------------------------------------------------------- /config/template/template_handler_service.descriptor_set: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-cloud-security/app-identity-and-access-adapter/200b26a419a5bbe1c625e4aee7c3bdb0bf561909/config/template/template_handler_service.descriptor_set -------------------------------------------------------------------------------- /config/template/template_proto.descriptor_set: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-cloud-security/app-identity-and-access-adapter/200b26a419a5bbe1c625e4aee7c3bdb0bf561909/config/template/template_proto.descriptor_set -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | This is an open source project, and we appreciate your help! 4 | 5 | We use the GitHub issue tracker to discuss new features and non-trivial bugs. In addition to the issue tracker, the [#appid-at-ibm Slack channel](https://ibm-cloud-success.slack.com/) is an easy way to get in touch with the maintainers. 6 | 7 | To contribute code, documentation, or tests, please submit a pull request to the GitHub repository. Generally, we expect two maintainers to review your pull request before it is approved for merging. For more details, see the MAINTAINERS page. 8 | -------------------------------------------------------------------------------- /crdcodegenerator/custom-boilerplate.go.txt: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright YEAR The Kubernetes Authors 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | -------------------------------------------------------------------------------- /crdcodegenerator/tools.go: -------------------------------------------------------------------------------- 1 | // +build tools 2 | 3 | /* 4 | Copyright 2019 The Kubernetes Authors. 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | */ 15 | 16 | // This package imports things required by build scripts, to force `go mod` to see them as dependencies 17 | package crdcodegenerator 18 | 19 | import _ "k8s.io/code-generator" 20 | -------------------------------------------------------------------------------- /crdcodegenerator/update-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | SCRIPT_ROOT=$(dirname ${BASH_SOURCE})/.. 8 | CODEGEN_PKG=${CODEGEN_PKG:-$(cd ${SCRIPT_ROOT}; ls -d -1 ./vendor/k8s.io/code-generator 2>/dev/null || echo ${GOPATH}/src/k8s.io/code-generator)} 9 | chmod +x vendor/k8s.io/code-generator/generate-groups.sh 10 | vendor/k8s.io/code-generator/generate-groups.sh all \ 11 | github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/client github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/pkg/apis \ 12 | policies:v1 \ 13 | --go-header-file ${SCRIPT_ROOT}/crdcodegenerator/custom-boilerplate.go.txt 14 | -------------------------------------------------------------------------------- /crdcodegenerator/verify-codegen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -o errexit 4 | set -o nounset 5 | set -o pipefail 6 | 7 | SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")/.. 8 | DIFFROOT="${SCRIPT_ROOT}/pkg" 9 | TMP_DIFFROOT="${SCRIPT_ROOT}/_tmp/pkg" 10 | _tmp="${SCRIPT_ROOT}/_tmp" 11 | 12 | cleanup() { 13 | rm -rf "${_tmp}" 14 | } 15 | trap "cleanup" EXIT SIGINT 16 | 17 | cleanup 18 | 19 | mkdir -p "${TMP_DIFFROOT}" 20 | cp -a "${DIFFROOT}"/* "${TMP_DIFFROOT}" 21 | 22 | "${SCRIPT_ROOT}/crdcodegenerator/update-codegen.sh" 23 | echo "diffing ${DIFFROOT} against freshly generated codegen" 24 | ret=0 25 | diff -Naupr "${DIFFROOT}" "${TMP_DIFFROOT}" || ret=$? 26 | cp -a "${TMP_DIFFROOT}"/* "${DIFFROOT}" 27 | if [[ $ret -eq 0 ]] 28 | then 29 | echo "${DIFFROOT} up to date." 30 | else 31 | echo "${DIFFROOT} is out of date. Please run crdcodegenerator/update-codegen.sh" 32 | exit 1 33 | fi 34 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/ibm-cloud-security/app-identity-and-access-adapter 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/PuerkitoBio/goquery v1.5.0 7 | github.com/dgrijalva/jwt-go/v4 v4.0.0-20190410170817-8222805572f2 8 | github.com/evanphx/json-patch v4.2.0+incompatible // indirect 9 | github.com/gogo/googleapis v1.2.0 10 | github.com/gogo/protobuf v1.2.1 11 | github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef 12 | github.com/golang/protobuf v1.3.1 // indirect 13 | github.com/google/gofuzz v1.0.0 // indirect 14 | github.com/googleapis/gnostic v0.2.0 // indirect 15 | github.com/gorilla/securecookie v1.1.1 16 | github.com/hashicorp/golang-lru v0.5.1 // indirect 17 | github.com/imdario/mergo v0.3.7 // indirect 18 | github.com/json-iterator/go v1.1.6 // indirect 19 | github.com/spf13/cobra v0.0.3 20 | github.com/stretchr/testify v1.3.0 21 | go.uber.org/zap v1.10.0 22 | golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f // indirect 23 | golang.org/x/sync v0.0.0-20190423024810-112230192c58 24 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect 25 | google.golang.org/grpc v1.20.1 26 | gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce 27 | istio.io/api v0.0.0-20190515205759-982e5c3888c6 28 | istio.io/istio v0.0.0-20190516081059-beb17827e164 29 | k8s.io/api v0.0.0-20190612125737-db0771252981 30 | k8s.io/apimachinery v0.0.0-20190612125636-6a5db36e93ad 31 | k8s.io/client-go v10.0.0+incompatible 32 | k8s.io/code-generator v0.0.0-20190612125529-c522cb6c26aa 33 | k8s.io/utils v0.0.0-20190506122338-8fab8cb257d5 // indirect 34 | ) 35 | 36 | replace ( 37 | golang.org/x/sync => golang.org/x/sync v0.0.0-20181108010431-42b317875d0f 38 | golang.org/x/sys => golang.org/x/sys v0.0.0-20190209173611-3b5209105503 39 | golang.org/x/tools => golang.org/x/tools v0.0.0-20190313210603-aa82965741a9 40 | k8s.io/api => k8s.io/api v0.0.0-20190612125737-db0771252981 41 | k8s.io/apimachinery => k8s.io/apimachinery v0.0.0-20190612125636-6a5db36e93ad 42 | k8s.io/client-go => k8s.io/client-go v0.0.0-20190612125919-5c45477a8ae7 43 | k8s.io/code-generator => k8s.io/code-generator v0.0.0-20190612125529-c522cb6c26aa 44 | k8s.io/component-base => k8s.io/component-base v0.0.0-20190612130303-4062e14deebe 45 | ) 46 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/.helmignore: -------------------------------------------------------------------------------- 1 | # Patterns to ignore when building packages. 2 | # This supports shell glob matching, relative path matching, and 3 | # negation (prefixed with !). Only one pattern per line. 4 | .DS_Store 5 | # Common VCS dirs 6 | .git/ 7 | .gitignore 8 | .bzr/ 9 | .bzrignore 10 | .hg/ 11 | .hgignore 12 | .svn/ 13 | # Common backup files 14 | *.swp 15 | *.bak 16 | *.tmp 17 | *~ 18 | # Various IDEs 19 | .project 20 | .idea/ 21 | *.tmproj 22 | .vscode/ 23 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/Chart.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | name: appidentityandaccessadapter 3 | namespace: istio-system 4 | version: 0.5.0 5 | description: A Helm chart for the App Identity and Access Adapter 6 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/appidentityandaccessadapter-0.5.0.tgz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-cloud-security/app-identity-and-access-adapter/200b26a419a5bbe1c625e4aee7c3bdb0bf561909/helm/appidentityandaccessadapter/appidentityandaccessadapter-0.5.0.tgz -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/index.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | entries: 3 | appidentityandaccessadapter: 4 | - apiVersion: v1 5 | created: 2020-01-30T11:47:01.463223-06:00 6 | description: A Helm chart for the App Identity and Access Adapter 7 | digest: 01fb58d3feeaac24549b2b202d87df8208c4b5bff78ddf81dc051a3ea4df7b3f 8 | name: appidentityandaccessadapter 9 | urls: 10 | - appidentityandaccessadapter-0.5.0.tgz 11 | version: 0.5.0 12 | generated: 2020-01-30T11:47:01.456838-06:00 13 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/NOTES.txt: -------------------------------------------------------------------------------- 1 | App Identity and Access Adapter installed successfully! -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/_helpers.tpl: -------------------------------------------------------------------------------- 1 | {{/* vim: set filetype=mustache: */}} 2 | {{/* 3 | Expand the name of the chart. 4 | */}} 5 | {{- define "appid-istio-mixer-adapter.name" -}} 6 | {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}} 7 | {{- end -}} 8 | 9 | {{/* 10 | Create a default fully qualified app name. 11 | We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). 12 | If release name contains chart name it will be used as a full name. 13 | */}} 14 | {{- define "appidentityandaccessadapter.fullname" -}} 15 | {{- if .Values.fullnameOverride -}} 16 | {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" -}} 17 | {{- else -}} 18 | {{- $name := default .Chart.Name .Values.nameOverride -}} 19 | {{- if contains $name .Release.Name -}} 20 | {{- .Release.Name | trunc 63 | trimSuffix "-" -}} 21 | {{- else -}} 22 | {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" -}} 23 | {{- end -}} 24 | {{- end -}} 25 | {{- end -}} 26 | 27 | {{/* 28 | Create chart name and version as used by the chart label. 29 | */}} 30 | {{- define "appidentityandaccessadapter.chart" -}} 31 | {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" -}} 32 | {{- end -}} 33 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/cluster-role-binding.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: rbac.authorization.k8s.io/v1 2 | kind: ClusterRoleBinding 3 | metadata: 4 | name: cluster-role-binding-appidentityandaccessadapter 5 | roleRef: 6 | apiGroup: rbac.authorization.k8s.io 7 | kind: ClusterRole 8 | name: cluster-admin 9 | subjects: 10 | - kind: ServiceAccount 11 | name: default 12 | namespace: istio-system -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/deployment.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: dpl-{{ .Values.appName }} 5 | namespace: istio-system 6 | labels: 7 | app: {{ .Values.appName }} 8 | spec: 9 | replicas: {{ .Values.replicaCount }} 10 | selector: 11 | matchLabels: 12 | app: {{ .Values.appName }} 13 | template: 14 | metadata: 15 | labels: 16 | app: {{ .Values.appName }} 17 | annotations: 18 | sidecar.istio.io/inject: "false" 19 | scheduler.alpha.kubernetes.io/critical-pod: "" 20 | spec: 21 | containers: 22 | - name: {{ .Values.appName }} 23 | image: {{ .Values.image.repository }}:{{ .Values.image.tag }} 24 | 25 | args: 26 | {{ if .Values.logging.json }} 27 | - "--json" 28 | {{ end }} 29 | - "--port={{ .Values.service.port }}" 30 | - "--level={{ .Values.logging.level }}" 31 | - "--hash-key={{ .Values.keys.hashKeySize }}" 32 | - "--block-key={{ .Values.keys.blockKeySize }}" 33 | 34 | imagePullPolicy: {{ .Values.image.pullPolicy}} 35 | ports: 36 | - containerPort: {{ .Values.service.port }} 37 | volumeMounts: 38 | - name: transient-storage 39 | mountPath: /volume 40 | volumes: 41 | - name: transient-storage 42 | emptyDir: {} 43 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/handler.yaml: -------------------------------------------------------------------------------- 1 | # handler for appidentityandaccessadapter adapter 2 | apiVersion: "config.istio.io/v1alpha2" 3 | kind: handler 4 | metadata: 5 | name: handler-{{ .Values.appName }} 6 | namespace: istio-system 7 | spec: 8 | adapter: appidentityandaccessadapter 9 | connection: 10 | address: svc-{{ .Values.appName }}:{{ .Values.service.port }} -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/instance.yaml: -------------------------------------------------------------------------------- 1 | # instance for authorization template 2 | apiVersion: "config.istio.io/v1alpha2" 3 | kind: instance 4 | metadata: 5 | name: instance-{{ .Values.appName }} 6 | namespace: istio-system 7 | spec: 8 | template: authnz 9 | params: 10 | target: 11 | path: request.url_path | "/" 12 | method: request.method | "get" 13 | service: destination.service.name | "" 14 | namespace: destination.service.namespace | "default" 15 | request: 16 | scheme: request.scheme | "" 17 | host: request.host | "" 18 | path: request.url_path | "" 19 | headers: 20 | cookies: request.headers["cookie"] | "" 21 | authorization: request.headers["authorization"] | "" 22 | params: 23 | code: request.query_params["code"] | "" 24 | error: request.query_params["error"] | "" 25 | state: request.query_params["state"] | "" 26 | 27 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/jwt-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 kind: CustomResourceDefinition metadata: name: jwtconfigs.security.cloud.ibm.com spec: group: security.cloud.ibm.com versions: - name: v1 served: true storage: true scope: Namespaced names: plural: jwtconfigs singular: jwtconfig kind: JwtConfig validation: openAPIV3Schema: properties: spec: required: - jwksUrl properties: jwksUrl: type: string pattern: '^(?:http(s)?:\/\/)?((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|((\d{1,3}\.){3}\d{1,3}))(\:\d+)?(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$' -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/keys.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: {{ .Values.appName }}-cookie-sig-enc-keys 5 | namespace: istio-system 6 | annotations: 7 | "helm.sh/hook": "pre-install" 8 | "helm.sh/hook-delete-policy": "before-hook-creation" 9 | labels: 10 | app: {{ .Values.appName }} 11 | chart: {{ .Chart.Name }} 12 | release: {{ .Release.Name }} 13 | heritage: {{ .Release.Service }} 14 | data: 15 | HASH_KEY: {{ randAlphaNum (.Values.keys.hashKeySize | int) | b64enc | quote }} 16 | BLOCK_KEY: {{ randAlphaNum (.Values.keys.blockKeySize | int) | b64enc | quote }} 17 | 18 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/oidc-config.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: oidcconfigs.security.cloud.ibm.com 5 | spec: 6 | group: security.cloud.ibm.com 7 | versions: 8 | - name: v1 9 | served: true 10 | storage: true 11 | scope: Namespaced 12 | names: 13 | plural: oidcconfigs 14 | singular: oidcconfig 15 | kind: OidcConfig 16 | validation: 17 | openAPIV3Schema: 18 | properties: 19 | spec: 20 | required: 21 | - clientId 22 | - discoveryUrl 23 | properties: 24 | authMethod: 25 | type: string 26 | enum: 27 | - client_secret_basic 28 | - client_secret_post 29 | clientId: 30 | type: string 31 | minLength: 1 32 | discoveryUrl: 33 | type: string 34 | pattern: '^(?:http(s)?:\/\/)?((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|((\d{1,3}\.){3}\d{1,3}))(\:\d+)?(\/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$' 35 | clientSecret: 36 | type: string 37 | minLength: 1 38 | clientSecretRef: 39 | type: object 40 | properties: 41 | name: 42 | type: string 43 | minLength: 1 44 | key: 45 | type: string 46 | minLength: 1 47 | required: 48 | - name 49 | - key 50 | scopes: 51 | type: array 52 | items: 53 | type: string 54 | minItems: 1 -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/rule.yaml: -------------------------------------------------------------------------------- 1 | # rule to dispatch the app ID adapter handler 2 | apiVersion: "config.istio.io/v1alpha2" 3 | kind: rule 4 | metadata: 5 | name: rule-{{ .Values.appName }} 6 | namespace: istio-system 7 | spec: 8 | actions: 9 | - handler: handler-{{ .Values.appName }}.istio-system 10 | instances: 11 | - instance-{{ .Values.appName }} 12 | name: a1 13 | requestHeaderOperations: 14 | - name: Authorization 15 | values: [ a1.output.authorization ] 16 | operation: REPLACE 17 | responseHeaderOperations: 18 | - name: Set-Cookie 19 | values: [ a1.output.sessionCookie ] 20 | operation: APPEND 21 | -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/templates/service.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: svc-{{ .Values.appName }} 5 | namespace: istio-system 6 | labels: 7 | app: {{ .Values.appName }} 8 | spec: 9 | type: {{ .Values.service.type }} 10 | ports: 11 | - name: grpc 12 | protocol: TCP 13 | port: {{ .Values.service.port }} 14 | targetPort: {{ .Values.service.port }} 15 | selector: 16 | app: {{ .Values.appName }} 17 | --- -------------------------------------------------------------------------------- /helm/appidentityandaccessadapter/values.yaml: -------------------------------------------------------------------------------- 1 | ## The values define the operation of the 2 | ## oidc / jwt Istio adapter helm chart 3 | ## 4 | ## The current values represent opinionated defaults 5 | ## but may be changed to better suit the use-case 6 | # 7 | 8 | appName: appidentityandaccessadapter 9 | 10 | ## Denotes the number of adapter replicas as part of the deployment. WARNING: due to 11 | ## current limitations, workloads using OIDC policies should not increment this number 12 | replicaCount: 1 13 | 14 | ## The image provides the docker source of the cloud adapter 15 | image: 16 | ## This is the source of the adapter docker image 17 | repository: ibmcloudsecurity/app-identity-and-access-adapter 18 | ## The image version to pull. Tag "latest" always contains 19 | ## the most up-to-date image. To avoid breaking changes during 20 | ## installs, it is recommended to use a tag. 21 | tag: 0.5.0 22 | ## The pullPolicy defines when the adapter image needs to be re-pulled 23 | ## from the source repository. Set to Always when using "latest." 24 | pullPolicy: Always 25 | 26 | ## Service defines the applications exposure 27 | service: 28 | # The service type to use. 29 | type: ClusterIP 30 | # The port the application should run on 31 | port: 47304 32 | 33 | ## These keys are primarily used for cookie encryption 34 | ## Symmetric signing and encryption keys are created on 35 | ## initialization. 36 | keys: 37 | ## The hashKey is required, used to authenticate 38 | ## the cookie value using HMAC. It is recommended 39 | ## to use a key with 32 or 64 bytes. 40 | hashKeySize: 32 41 | ## The blockKey is used to encrypt the cookie value. 42 | ## The length must correspond to the block size of the AES encryption algorithm. 43 | ## Valid lengths are 16, 24, or 32 bytes to select AES-128, AES-192, or AES-256. 44 | blockKeySize: 16 45 | 46 | ## Logging is facilitated by the zapcore uber library https://godoc.org/go.uber.org/zap/zapcore 47 | logging: 48 | 49 | ## By default logging is output in a json format 50 | ## which can be easily consumed by the 3rd party 51 | ## logging systems. Setting this value to false 52 | ## produces a more easily readable string format 53 | json: true 54 | 55 | ## The log output level defaults to 0 [INFO]. For more 56 | ## voluminous logging, use DEBUG (-1). Logging can be configured 57 | ## from -1 to 7 58 | ## See zapcore: https://godoc.org/go.uber.org/zap/zapcore#Level 59 | level: 0 60 | -------------------------------------------------------------------------------- /images/istio-adapter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ibm-cloud-security/app-identity-and-access-adapter/200b26a419a5bbe1c625e4aee7c3bdb0bf561909/images/istio-adapter.png -------------------------------------------------------------------------------- /maintainers.md: -------------------------------------------------------------------------------- 1 | ## Maintainers 2 | 3 | This file lists the official maintainers of the App Identity and Access Adapter project. Check out the following list to see who needs to review your pull request. Two maintainer approvals are required to merge a change. 4 | 5 | | Role | Person | GH ID | 6 | |------|------|---------| 7 | | Architect | Anton Aleksandrov | @aal80 | 8 | | Architect | Tal Aviel | @TalAviel | 9 | | Development | Ishan Gulhane | @ishangulhane | 10 | | Development | Kimmy Taft | @kimmytaft | 11 | | Development | Joyce Huang | @huangjoyce3 | 12 | | Documentation | Shawna Guilianelli | @smguilia | 13 | 14 | 15 | ## Past Maintainers 16 | This is kept as a 'thank you' for the contributions of past maintainers. 17 | 18 | Aaron Libertore 19 | Anup Rokkam 20 | -------------------------------------------------------------------------------- /samples/app/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /samples/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:8 2 | WORKDIR "/app" 3 | ADD . /app 4 | RUN npm install 5 | ENV NODE_TLS_REJECT_UNAUTHORIZED=0 6 | EXPOSE 8000 7 | CMD ["npm", "start"] 8 | -------------------------------------------------------------------------------- /samples/app/README.md: -------------------------------------------------------------------------------- 1 | # Sample App 2 | 3 | > Sample app to be used for Istio adapter testing 4 | 5 | ## Configure and Deploy 6 | 7 | 1. Enure your kubectl environment to use your second cluster  8 | ```bash 9 | $ kubectl label namespace sample-app istio-injection=enabled 10 | ``` 11 | 12 | 2. Inject the Istio sidecar into your deployment 13 | 14 | ```bash 15 | $ istioctl kube-inject -f ./sample-app.yaml | kubectl apply -f - 16 | ``` -------------------------------------------------------------------------------- /samples/app/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const log4js = require('log4js'); 3 | 4 | const logger = log4js.getLogger('istio-sample-app'); 5 | logger.level = process.env['LOG_LEVEL'] || 'trace'; 6 | 7 | const app = express(); 8 | 9 | app.get('/', (req, res) => { 10 | return res.status(200).send({ 11 | routes: [ 12 | "/web/home", 13 | "/web/home/:id", 14 | "/web/user", 15 | "/api/headers", 16 | "/api/headers/:id", 17 | ] 18 | }) 19 | }); 20 | 21 | const handler = (req, res) => { 22 | res.status(200).send(req.headers) 23 | }; 24 | 25 | // Frontend 26 | app.get('/web/home', handler); 27 | app.get('/web/home/:id', handler); 28 | app.get('/web/user', handler); 29 | 30 | // API 31 | app.get('/api/headers', handler); 32 | app.post('/api/headers',handler); 33 | app.put('/api/headers', handler); 34 | app.delete('/api/headers', handler); 35 | app.patch('/api/headers', handler); 36 | 37 | // API depth 2 38 | app.get('/api/headers/:id', handler); 39 | app.post('/api/headers/:id', handler); 40 | app.put('/api/headers/:id', handler); 41 | app.delete('/api/headers/:id', handler); 42 | app.patch('/api/headers/:id', handler); 43 | 44 | app.listen(8000, () => { 45 | logger.info('Listening on port 8000'); 46 | }); 47 | -------------------------------------------------------------------------------- /samples/app/cicd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | APP_NAME=app-identity-and-access-adapter-sample-app 3 | APP_VERSION=latest 4 | IMAGE_REGISTRY_NAMESPACE=ibmcloudsecurity 5 | IMAGE_TAG=${IMAGE_REGISTRY_NAMESPACE}/${APP_NAME}:${APP_VERSION} 6 | 7 | IMAGE_TAG=${IMAGE_REGISTRY_NAMESPACE}/${APP_NAME}:${APP_VERSION} 8 | 9 | echo Building Docker image 10 | docker build -t $IMAGE_TAG . 11 | 12 | echo Pushing Docker image to container registry 13 | docker push $IMAGE_TAG -------------------------------------------------------------------------------- /samples/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-cloud-fund-backend", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "start": "node $NODE_DEBUG_OPTION ." 8 | }, 9 | "keywords": [], 10 | "author": "Aaron Libeatore", 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "express": "^4.16.4", 14 | "jsonwebtoken": "^8.4.0", 15 | "log4js": "^3.0.6" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /samples/app/sample-app.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Namespace 3 | metadata: 4 | name: sample-app 5 | --- 6 | apiVersion: networking.istio.io/v1alpha3 7 | kind: Gateway 8 | metadata: 9 | name: gw-sample-app 10 | namespace: sample-app 11 | spec: 12 | selector: 13 | istio: ingressgateway # use Istio default gateway implementation 14 | servers: 15 | - port: 16 | name: http 17 | number: 80 18 | protocol: HTTP 19 | hosts: 20 | - "*" 21 | --- 22 | apiVersion: networking.istio.io/v1alpha3 23 | kind: VirtualService 24 | metadata: 25 | name: vsvc-sample-app 26 | namespace: sample-app 27 | spec: 28 | hosts: 29 | - "*" 30 | gateways: 31 | - gw-sample-app 32 | http: 33 | - match: 34 | - uri: 35 | prefix: /api 36 | - uri: 37 | prefix: / 38 | route: 39 | - destination: 40 | port: 41 | number: 8000 42 | host: svc-sample-app 43 | --- 44 | apiVersion: apps/v1 45 | kind: Deployment 46 | metadata: 47 | name: dpl-sample-app 48 | namespace: sample-app 49 | labels: 50 | app: sample-app 51 | spec: 52 | replicas: 1 53 | selector: 54 | matchLabels: 55 | app: sample-app 56 | template: 57 | metadata: 58 | labels: 59 | app: sample-app 60 | spec: 61 | containers: 62 | - name: sample-app 63 | image: ibmcloudsecurity/app-identity-and-access-adapter-sample-app:latest 64 | imagePullPolicy: Always 65 | ports: 66 | - containerPort: 8000 67 | --- 68 | apiVersion: v1 69 | kind: Service 70 | metadata: 71 | name: svc-sample-app 72 | namespace: sample-app 73 | labels: 74 | app: sample-app 75 | spec: 76 | type: ClusterIP 77 | ports: 78 | - name: http 79 | port: 8000 80 | protocol: TCP 81 | selector: 82 | app: sample-app 83 | -------------------------------------------------------------------------------- /samples/crds/samplejwtconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "security.cloud.ibm.com/v1" 2 | kind: JwtConfig 3 | metadata: 4 | name: jwt-config 5 | namespace: sample-app 6 | spec: 7 | jwksUrl: https://us-south.appid.cloud.ibm.com/oauth/v4//publickeys 8 | -------------------------------------------------------------------------------- /samples/crds/sampleoidcconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "security.cloud.ibm.com/v1" 2 | kind: OidcConfig 3 | metadata: 4 | name: oidc-provider-config 5 | namespace: sample-app 6 | spec: 7 | authMethod: client_secret_basic 8 | discoveryUrl: https://us-south.appid.cloud.ibm.com/oauth/v4//.well-known/openid-configuration 9 | clientId: 10 | clientSecret: 11 | -------------------------------------------------------------------------------- /samples/crds/sampleoidcconfigwithsecretref.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "security.cloud.ibm.com/v1" 2 | kind: OidcConfig 3 | metadata: 4 | name: oidc-provider-config-with-secret-ref 5 | namespace: sample-app 6 | spec: 7 | authMethod: client_secret_basic 8 | discoveryUrl: https://us-south.appid.cloud.ibm.com/oauth/v4//oidc-discovery/.well-known 9 | clientId: 10 | clientSecretRef: 11 | name: oidc-config-secret # 12 | key: secret # 13 | -------------------------------------------------------------------------------- /samples/crds/samplepolicy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "security.cloud.ibm.com/v1" 2 | kind: Policy 3 | metadata: 4 | name: samplepolicy 5 | namespace: sample-app 6 | spec: 7 | targets: 8 | - 9 | serviceName: 10 | paths: 11 | - exact: /web/home 12 | method: ALL 13 | policies: 14 | - policyType: oidc 15 | config: 16 | rules: 17 | - claim: scope 18 | match: ALL 19 | source: access_token 20 | values: 21 | - openid 22 | - claim: amr 23 | match: ANY 24 | source: id_token 25 | values: 26 | - google 27 | 28 | - exact: /web/user 29 | method: GET 30 | policies: 31 | - policyType: oidc 32 | config: 33 | redirectUri: https://github.com/ibm-cloud-security/app-identity-and-access-adapter 34 | - prefix: / 35 | method: ALL 36 | policies: 37 | - 38 | policyType: jwt 39 | config: samplejwtconfig -------------------------------------------------------------------------------- /samples/crds/secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: oidc-config-secret 5 | namespace: sample-app 6 | type: Opaque 7 | data: 8 | secret: c2VjcmV0 # -------------------------------------------------------------------------------- /tests/fake/auth_server.go: -------------------------------------------------------------------------------- 1 | package fake 2 | 3 | import ( 4 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/authserver" 5 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/authserver/keyset" 6 | ) 7 | 8 | type AuthServer struct { 9 | Keys keyset.KeySet 10 | Url string 11 | JwksURL string 12 | TknEndpoint string 13 | AuthEndpoint string 14 | UserInfoURL string 15 | } 16 | 17 | func NewAuthServer() *AuthServer { 18 | return &AuthServer{ 19 | JwksURL: "https://auth.com/publickeys", 20 | TknEndpoint: "https://auth.com/token", 21 | AuthEndpoint: "https://auth.com/authorization", 22 | UserInfoURL: "https://auth.com/userinfo", 23 | Keys: &KeySet{}, 24 | } 25 | } 26 | func (m *AuthServer) JwksEndpoint() string { 27 | return m.JwksURL 28 | } 29 | 30 | func (m *AuthServer) TokenEndpoint() string { 31 | return m.TknEndpoint 32 | } 33 | 34 | func (m *AuthServer) AuthorizationEndpoint() string { 35 | return m.AuthEndpoint 36 | } 37 | 38 | func (m *AuthServer) UserInfoEndpoint() string { 39 | return m.UserInfoURL 40 | } 41 | 42 | func (m *AuthServer) KeySet() keyset.KeySet { 43 | return m.Keys 44 | } 45 | 46 | func (m *AuthServer) SetKeySet(k keyset.KeySet) { 47 | m.Keys = k 48 | } 49 | 50 | func (m *AuthServer) GetTokens(authnMethod string, clientID string, clientSecret string, authorizationCode string, redirectURI string, refreshToken string) (*authserver.TokenResponse, error) { 51 | return nil, nil 52 | } 53 | -------------------------------------------------------------------------------- /tests/fake/client.go: -------------------------------------------------------------------------------- 1 | package fake 2 | 3 | import ( 4 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/adapter/authserver" 5 | ) 6 | 7 | type TokenResponse struct { 8 | Res *authserver.TokenResponse 9 | Err error 10 | } 11 | 12 | type Client struct { 13 | Server authserver.AuthorizationServerService 14 | TokenResponse *TokenResponse 15 | ClientName string 16 | ClientID string 17 | ClientSecret string 18 | Scopes []string 19 | } 20 | 21 | func NewClient(tokenResponse *TokenResponse) *Client { 22 | return &Client{ 23 | Server: NewAuthServer(), 24 | ClientName: "name", 25 | ClientID: "id", 26 | ClientSecret: "secret", 27 | TokenResponse: tokenResponse, 28 | } 29 | } 30 | 31 | func (m *Client) Name() string { 32 | return m.ClientName 33 | } 34 | 35 | func (m *Client) ID() string { 36 | return m.ClientID 37 | } 38 | 39 | func (m *Client) Secret() string { 40 | return m.ClientSecret 41 | } 42 | 43 | func (m *Client) Scope() string { 44 | return "openid profile email" 45 | } 46 | 47 | func (m *Client) AuthorizationServer() authserver.AuthorizationServerService { 48 | return m.Server 49 | } 50 | 51 | func (m *Client) ExchangeGrantCode(code string, redirectURI string) (*authserver.TokenResponse, error) { 52 | return m.TokenResponse.Res, m.TokenResponse.Err 53 | } 54 | 55 | func (m *Client) RefreshToken(refreshToken string) (*authserver.TokenResponse, error) { 56 | return m.TokenResponse.Res, m.TokenResponse.Err 57 | } 58 | -------------------------------------------------------------------------------- /tests/fake/keyset.go: -------------------------------------------------------------------------------- 1 | package fake 2 | 3 | import "crypto" 4 | 5 | type KeySet struct { 6 | url string 7 | } 8 | 9 | func (k *KeySet) PublicKeyURL() string { return k.url } 10 | func (k *KeySet) PublicKey(kid string) crypto.PublicKey { return nil } 11 | -------------------------------------------------------------------------------- /tests/framework/context.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | ) 8 | 9 | const ( 10 | clusterRoot = "CLUSTER_ROOT" 11 | kubeConfig = "KUBECONFIG" 12 | ) 13 | 14 | const ( 15 | templatesDir = "../templates" 16 | JwtConfigTemplate = templatesDir + "/jwtconfig.yaml" 17 | OidcConfigTemplate = templatesDir + "/oidcconfig.yaml" 18 | PolicyTemplate = templatesDir + "/policy.yaml" 19 | ) 20 | 21 | // Env models the kubernetes environment 22 | type Env struct { 23 | ClusterRoot string 24 | KubeConfig string 25 | } 26 | 27 | // NewEnv returns a new ENV instance 28 | func NewEnv() *Env { 29 | return &Env{ 30 | os.Getenv(clusterRoot), 31 | os.Getenv(kubeConfig), 32 | } 33 | } 34 | 35 | // Context models the test environment 36 | type Context struct { 37 | testID string 38 | CRDManager *CRDManager 39 | AppIDManager *AppIDManager 40 | Env *Env 41 | SessionCookie *http.Cookie 42 | client *http.Client 43 | } 44 | 45 | // NewContext creates a new test suite context 46 | func NewContext(testID string) *Context { 47 | ctx := &Context{ 48 | testID: testID, 49 | Env: NewEnv(), 50 | AppIDManager: NewAppIDManager(), 51 | client: &http.Client{}, 52 | } 53 | mgr := &CRDManager{context: ctx} 54 | ctx.CRDManager = mgr 55 | return ctx 56 | } 57 | 58 | // SendRequest issues a new http request with an authorization header 59 | func (c *Context) SendRequest(method string, path string, headers map[string]string) (*http.Response, error) { 60 | req, err := http.NewRequest(method, c.Env.ClusterRoot+path, nil) 61 | if err != nil { 62 | fmt.Printf("Could not send request %s\n", err.Error()) 63 | return nil, err 64 | } 65 | for k, v := range headers { 66 | req.Header.Set(k, v) 67 | } 68 | 69 | return c.client.Do(req) 70 | } 71 | 72 | // StopHttpRedirects configures the default client to ignore HTTP redirects 73 | func (c *Context) StopHttpRedirects() { 74 | c.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 75 | return http.ErrUseLastResponse 76 | } 77 | } 78 | 79 | // EnableRedirects configures the default client to follow HTTP redirects 80 | func (c *Context) EnableRedirects() { 81 | c.client.CheckRedirect = func(req *http.Request, via []*http.Request) error { 82 | return nil 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /tests/framework/crd_manager.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "fmt" 5 | "math/rand" 6 | "os" 7 | "strings" 8 | "text/template" 9 | 10 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/framework/utils" 11 | ) 12 | 13 | const ( 14 | letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" 15 | yamlExt = ".yaml" 16 | ) 17 | 18 | // CRD models a Kubernetes CRD exposing name and namespace 19 | type CRD interface { 20 | GetName() string 21 | GetNamespace() string 22 | } 23 | 24 | type crd struct { 25 | CRD 26 | pathToYAML string 27 | } 28 | 29 | // CRD Manager maintains Kubernetes CRDs locally and provides helper methods create/update/delete them 30 | type CRDManager struct { 31 | crds []crd 32 | 33 | context *Context 34 | } 35 | 36 | // CleanUp delete stored CRDs from Kubernetes then purges the local store 37 | func (m *CRDManager) CleanUp() error { 38 | for _, crd := range m.crds { 39 | err := utils.KubeDelete(crd.GetNamespace(), crd.pathToYAML, m.context.Env.KubeConfig) 40 | err = os.Remove(crd.pathToYAML) 41 | if err != nil { 42 | fmt.Println(err.Error()) 43 | } 44 | } 45 | m.crds = []crd{} 46 | return nil 47 | } 48 | 49 | // AddCRD adds a custom resource definition from a given file 50 | func (m *CRDManager) AddCRD(pathToTemplate string, data CRD) error { 51 | t, err := template.ParseFiles(pathToTemplate) 52 | if err != nil { 53 | return err 54 | } 55 | 56 | file := strings.Split(pathToTemplate, yamlExt)[0] 57 | 58 | tmpPath := file + "-" + RandString(4) + yamlExt 59 | f, err := os.Create(tmpPath) 60 | if err != nil { 61 | return err 62 | } 63 | 64 | err = t.Execute(f, &data) 65 | if err != nil { 66 | return err 67 | } 68 | _ = f.Close() 69 | 70 | err = utils.KubeApply(data.GetNamespace(), tmpPath, m.context.Env.KubeConfig) 71 | if err != nil { 72 | _ = os.Remove(tmpPath) 73 | return err 74 | } 75 | 76 | m.crds = append(m.crds, crd{ 77 | data, 78 | tmpPath, 79 | }) 80 | 81 | return nil 82 | } 83 | 84 | // DeleteCRD deletes a custom resource definition using a given CRD 85 | func (m *CRDManager) DeleteCRD(savedCRD CRD) error { 86 | for i, crd := range m.crds { 87 | if crd.GetName() == savedCRD.GetName() && crd.GetNamespace() == savedCRD.GetNamespace() { 88 | err := utils.KubeDelete(crd.GetNamespace(), crd.pathToYAML, m.context.Env.KubeConfig) 89 | if err != nil { 90 | return err 91 | } 92 | err = os.Remove(crd.pathToYAML) 93 | if err != nil { 94 | return err 95 | } 96 | m.crds = remove(m.crds, i) 97 | return nil 98 | } 99 | } 100 | return fmt.Errorf("crd not found") 101 | } 102 | 103 | func remove(s []crd, i int) []crd { 104 | s[len(s)-1], s[i] = s[i], s[len(s)-1] 105 | return s[:len(s)-1] 106 | } 107 | 108 | func RandString(n int) string { 109 | b := make([]byte, n) 110 | for i := range b { 111 | b[i] = letterBytes[rand.Intn(len(letterBytes))] 112 | } 113 | return string(b) 114 | } 115 | -------------------------------------------------------------------------------- /tests/framework/framework.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | "sync" 8 | "testing" 9 | "time" 10 | ) 11 | 12 | // test.Run uses 0, 1, 2 exit codes. Use different exit codes for our framework. 13 | const ( 14 | 15 | // Indicates a framework-level init error 16 | exitCodeInitError = -1 17 | 18 | // Indicates an error due to the setup function supplied by the user 19 | exitCodeSetupError = -2 20 | ) 21 | 22 | var ( 23 | rt *Context 24 | rtMu sync.Mutex 25 | ) 26 | 27 | // mRunFn abstracts testing.M.run, so that the framework itself can be tested. 28 | type mRunFn func() int 29 | 30 | type ModifierFn func(ctx *Context) error 31 | 32 | // Suite allows the test author to specify suite-related metadata and do setup in a fluent-style, before commencing execution. 33 | type Suite struct { 34 | testID string 35 | mRun mRunFn 36 | osExit func(int) 37 | setupFns []ModifierFn 38 | cleanupFns []ModifierFn 39 | ctx *Context 40 | } 41 | 42 | // NewSuite returns a new suite instance. 43 | func NewSuite(testID string, m *testing.M) *Suite { 44 | return newSuite(testID, m.Run, os.Exit) 45 | } 46 | 47 | func newSuite(testID string, fn mRunFn, osExit func(int)) *Suite { 48 | s := &Suite{ 49 | testID: testID, 50 | mRun: fn, 51 | osExit: osExit, 52 | } 53 | 54 | return s 55 | } 56 | 57 | // Setup runs enqueues the given setup function to run before test execution. 58 | func (s *Suite) Setup(fn ModifierFn) *Suite { 59 | s.setupFns = append(s.setupFns, fn) 60 | return s 61 | } 62 | 63 | // Cleanup runs enqueues the given cleanup function to run after test execution. 64 | func (s *Suite) Cleanup(fn ModifierFn) *Suite { 65 | s.cleanupFns = append(s.cleanupFns, fn) 66 | return s 67 | } 68 | 69 | // Run the suite. This method calls os.Exit and does not return. 70 | func (s *Suite) Run() { 71 | s.osExit(s.run()) 72 | } 73 | 74 | func (s *Suite) run() (errLevel int) { 75 | if err := initRuntimeContext(s.testID); err != nil { 76 | return exitCodeInitError 77 | } 78 | 79 | s.ctx = rt 80 | 81 | fmt.Printf("=== Suite %q starting ===\n", s.testID) 82 | 83 | start := time.Now() 84 | 85 | if err := s.runModifierFns(rt, s.setupFns); err != nil { 86 | fmt.Printf("Exiting due to setup failure: %v\n", err) 87 | return exitCodeSetupError 88 | } 89 | 90 | defer func() { 91 | end := time.Now() 92 | fmt.Printf("=== Suite %q run time: %v ===\n", s.testID, end.Sub(start)) 93 | }() 94 | 95 | errLevel = s.mRun() 96 | if errLevel != 0 { 97 | fmt.Printf("=== FAILED: Suite %q (exitCode: %v) ===\n", s.testID, errLevel) 98 | } 99 | 100 | if err := s.runModifierFns(rt, s.cleanupFns); err != nil { 101 | fmt.Printf("Cleanup failed %v\n", err) 102 | } 103 | 104 | return errLevel 105 | } 106 | 107 | func (s *Suite) runModifierFns(ctx *Context, fns []ModifierFn) (err error) { 108 | for _, fn := range fns { 109 | err := fn(ctx) 110 | if err != nil { 111 | return err 112 | } 113 | } 114 | return nil 115 | } 116 | 117 | func initRuntimeContext(testID string) error { 118 | rtMu.Lock() 119 | defer rtMu.Unlock() 120 | 121 | if rt != nil { 122 | return errors.New("framework is already initialized") 123 | } 124 | 125 | rt = NewContext(testID) 126 | return nil 127 | } 128 | -------------------------------------------------------------------------------- /tests/framework/test.go: -------------------------------------------------------------------------------- 1 | package framework 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type Test struct { 8 | ctx *Context 9 | 10 | goTest *testing.T 11 | 12 | skipCleanup bool 13 | } 14 | 15 | func NewTest(t *testing.T) *Test { 16 | rtMu.Lock() 17 | defer rtMu.Unlock() 18 | 19 | if rt == nil { 20 | panic("Test framework not initialized") 21 | } 22 | 23 | runner := &Test{ 24 | ctx: rt, 25 | goTest: t, 26 | skipCleanup: false, 27 | } 28 | 29 | return runner 30 | } 31 | 32 | func (t *Test) Run(fn func(ctx *Context)) { 33 | fn(t.ctx) 34 | if !t.skipCleanup { 35 | _ = t.ctx.CRDManager.CleanUp() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/framework/utils/helm.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | // HelmInit init helm with a service account 4 | func HelmInit(serviceAccount string) error { 5 | _, err := Shell("helm init --upgrade --service-account %s", serviceAccount) 6 | return err 7 | } 8 | 9 | // HelmClientInit initializes the Helm client only 10 | func HelmClientInit() error { 11 | _, err := Shell("helm init --client-only") 12 | return err 13 | } 14 | 15 | // HelmInstallDryRun helm install dry run from a chart for a given namespace 16 | func HelmInstallDryRun(chartDir, chartName, valueFile, namespace, setValue string) error { 17 | _, err := Shell("helm install --dry-run --debug " + HelmParams(chartDir, chartName, valueFile, namespace, setValue)) 18 | return err 19 | } 20 | 21 | // HelmInstall helm install from a chart for a given namespace 22 | // --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) 23 | func HelmInstall(chartDir, chartName, valueFile, namespace, setValue string) error { 24 | _, err := Shell("helm install " + HelmParams(chartDir, chartName, valueFile, namespace, setValue)) 25 | return err 26 | } 27 | 28 | // HelmTest helm test a chart release 29 | func HelmTest(releaseName string) error { 30 | _, err := Shell("helm test %s", releaseName) 31 | return err 32 | } 33 | 34 | // HelmTemplate helm template from a chart for a given namespace 35 | // --set stringArray set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2) 36 | func HelmTemplate(chartDir, chartName, namespace, setValue, outfile string) error { 37 | _, err := Shell("helm template %s --name %s --namespace %s %s > %s", chartDir, 38 | chartName, namespace, setValue, outfile) 39 | return err 40 | } 41 | 42 | // HelmDelete helm del --purge a chart 43 | func HelmDelete(chartName string) error { 44 | _, err := Shell("helm del --purge %s", chartName) 45 | return err 46 | } 47 | 48 | // HelmParams provides a way to construct helm params 49 | func HelmParams(chartDir, chartName, valueFile, namespace, setValue string) string { 50 | helmCmd := chartDir + " --name " + chartName + " --namespace " + namespace + " " + setValue 51 | if valueFile != "" { 52 | helmCmd = chartDir + " --name " + chartName + " --values " + valueFile + " --namespace " + namespace + " " + setValue 53 | } 54 | 55 | return helmCmd 56 | } 57 | -------------------------------------------------------------------------------- /tests/framework/utils/kube.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func kubeCommand(subCommand, namespace, yamlFileName string, kubeconfig string) string { 8 | if kubeconfig != "" { 9 | kubeconfig = "--kubeconfig=" + kubeconfig 10 | } 11 | 12 | if namespace == "" { 13 | return fmt.Sprintf("kubectl %s -f %s %s", subCommand, yamlFileName, kubeconfig) 14 | } 15 | return fmt.Sprintf("kubectl %s -n %s -f %s %s", subCommand, namespace, yamlFileName, kubeconfig) 16 | } 17 | 18 | // KubeApply kubectl apply from file 19 | func KubeApply(namespace, yamlFileName string, kubeconfig string) error { 20 | _, err := ShellMuteOutput(kubeCommand("apply", namespace, yamlFileName, kubeconfig)) 21 | return err 22 | } 23 | 24 | // KubeDelete kubectl delete from file 25 | func KubeDelete(namespace, yamlFileName string, kubeconfig string) error { 26 | _, err := ShellMuteOutput(kubeCommand("delete", namespace, yamlFileName, kubeconfig)) 27 | return err 28 | } 29 | -------------------------------------------------------------------------------- /tests/framework/utils/shell.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "os/exec" 7 | "strings" 8 | ) 9 | 10 | // Shell runs a command on shell and get back output and error if get one 11 | func Shell(format string, args ...interface{}) (string, error) { 12 | return sh(context.Background(), format, true, true, true, args...) 13 | } 14 | 15 | // ShellMuteOutput runs a command on shell and get back output and error if get one 16 | func ShellMuteOutput(format string, args ...interface{}) (string, error) { 17 | return sh(context.Background(), format, false, false, false, args...) 18 | } 19 | 20 | // Exists checks if an executable exists 21 | func Exists(executable string) bool { 22 | res, err := ShellMuteOutput("command -v %s", executable) 23 | if err != nil { 24 | return false 25 | } 26 | return res != "" 27 | } 28 | 29 | func sh(ctx context.Context, format string, logCommand, logOutput, logError bool, args ...interface{}) (string, error) { 30 | command := fmt.Sprintf(format, args...) 31 | if logCommand { 32 | fmt.Printf("Running command %s\n", command) 33 | } 34 | c := exec.CommandContext(ctx, "sh", "-c", command) // #nosec 35 | bytes, err := c.CombinedOutput() 36 | if logOutput { 37 | if output := strings.TrimSuffix(string(bytes), "\n"); len(output) > 0 { 38 | fmt.Printf("Command output: \n%s\n", output) 39 | } 40 | } 41 | 42 | if err != nil { 43 | if logError { 44 | fmt.Printf("Command error: %v\n", err) 45 | } 46 | return string(bytes), fmt.Errorf("command failed: %q %v", string(bytes), err) 47 | } 48 | return string(bytes), nil 49 | } 50 | -------------------------------------------------------------------------------- /tests/integration/helm/helm_test.go: -------------------------------------------------------------------------------- 1 | package helm 2 | 3 | import ( 4 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/framework" 5 | "testing" 6 | ) 7 | 8 | func setupENV(ctx *framework.Context) error { 9 | println("Special setup is running") 10 | return nil 11 | } 12 | 13 | func cleanupENV(ctx *framework.Context) error { 14 | return nil 15 | } 16 | 17 | func TestMain(t *testing.M) { 18 | framework. 19 | NewSuite("Helm tests", t). 20 | Setup(setupENV). 21 | Cleanup(cleanupENV). 22 | Run() 23 | } 24 | 25 | func TestHelmInstall(t *testing.T) { 26 | /* 27 | err := utils.HelmInstall("../../../helm/appidentityandaccessadapter", "appidentityandaccessadapter", "../../../helm/appidentityandaccessadapter/values.yaml", "istio-system", "") 28 | if err != nil { 29 | println(err.Error()) 30 | t.Fail() 31 | } 32 | */ 33 | } 34 | -------------------------------------------------------------------------------- /tests/integration/sample/sample_test.go: -------------------------------------------------------------------------------- 1 | package sample 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/framework" 8 | "github.com/ibm-cloud-security/app-identity-and-access-adapter/tests/framework/utils" 9 | ) 10 | 11 | // 12 | // Create a before method to setup a suite before tests execute 13 | // 14 | func before(ctx *framework.Context) error { 15 | _, _ = utils.Shell("ls") 16 | fmt.Printf("Before test method : %v\n", ctx) 17 | return nil 18 | } 19 | 20 | // 21 | // Create a cleanup method to execute once a suite has completed 22 | // 23 | func after(ctx *framework.Context) error { 24 | fmt.Printf("After test method : %v\n", ctx) 25 | return nil 26 | } 27 | 28 | // 29 | // Test main runs before ALL suite methods run 30 | // and begins test execution 31 | // 32 | func TestMain(t *testing.M) { 33 | framework. 34 | NewSuite("sample_test", t). 35 | Setup(before). 36 | Cleanup(after). 37 | Run() 38 | } 39 | 40 | // 41 | // Example: test calls the sample application's endpoint 42 | // 43 | func TestOK(t *testing.T) { 44 | framework. 45 | NewTest(t). 46 | Run(func(ctx *framework.Context) { 47 | _, _ = ctx.SendRequest("GET", "/", nil) 48 | }) 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/integration/templates/jwtconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "security.cloud.ibm.com/v1" 2 | kind: JwtConfig 3 | metadata: 4 | name: {{.Name}} 5 | spec: 6 | jwksUrl: {{.Spec.JwksURL}} -------------------------------------------------------------------------------- /tests/integration/templates/oidcconfig.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "security.cloud.ibm.com/v1" 2 | kind: OidcConfig 3 | metadata: 4 | name: {{.Name}} 5 | spec: 6 | authMethod: client_secret_basic 7 | oidcClientName: {{.Spec.ClientName}} 8 | clientId: {{.Spec.ClientID}} 9 | discoveryUrl: {{.Spec.DiscoveryURL}} 10 | clientSecret: {{.Spec.ClientSecret}} -------------------------------------------------------------------------------- /tests/integration/templates/policy.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: "security.cloud.ibm.com/v1" 2 | kind: Policy 3 | metadata: 4 | name: {{.Name}} 5 | spec: 6 | targets: 7 | {{- range .Spec.Target }} 8 | - 9 | serviceName: {{.ServiceName}} 10 | paths: {{ range .Paths }} 11 | - 12 | {{- if ne .Exact ""}} 13 | exact: {{.Exact}} 14 | {{- end }} 15 | {{- if ne .Prefix ""}} 16 | prefix: {{.Prefix}} 17 | {{- end }} 18 | {{- if ne .Method ""}} 19 | method: {{.Method}} 20 | {{- end }} 21 | policies: {{ range .Policies }} 22 | - 23 | policyType: {{ .PolicyType }} 24 | config: {{ .Config }} 25 | {{- if ne .RedirectUri ""}} 26 | redirectUri: {{ .RedirectUri }} 27 | {{- end }} 28 | {{- $length := len .Rules }} {{ if eq $length 0 }} 29 | rules: [] 30 | {{ else }} 31 | rules: {{- range .Rules }} 32 | - 33 | {{- if ne .Claim ""}} 34 | claim: {{.Claim}} 35 | {{- end}} 36 | {{- if ne .Match ""}} 37 | match: {{.Match}} 38 | {{- end}} 39 | {{- if ne .Source ""}} 40 | source: {{.Source}} 41 | {{- end}} 42 | values: 43 | {{- range .Values}} 44 | - {{ . }} 45 | {{- end}} 46 | {{- end }} 47 | {{- end }} 48 | {{- end }} 49 | {{- end }} 50 | {{- end }} -------------------------------------------------------------------------------- /tests/keys/key.private: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDelpuXf3J5OyDX 3 | gfJVCkGvRRv5YuRQXkJXFNrr/+tIlXYfBnVcvy48PVqibft2HjJqvv6TlErVFn4G 4 | sM1OlinvwiQVOl50lWurG6Gyh+hke5bgX2j7trpidXTLubzJmXeUmUZH208Ywap+ 5 | Ay8r7TzIfUeedouLbaIfD9L8n5gFpIkItzAjp2DUkdajL/C55jz4otyZTPPEFYyA 6 | hSLMmx9elP1nOaNNrNcihQpm8KK+5svV9EIZoI51VGuOt4mkJabIXazb5mRFovWw 7 | PABIZ7Fq2dvkP+dIZwN7l30RTFqcMhly3ZJSJtL91RZhlP3SjQJswykLvaOR6UUz 8 | sa0BtP/1AgMBAAECggEAYzw+DeLSBF5qG+phQaya8Csdd6exJmt/pBc2KvUjzm5k 9 | BRz6AelyEYVA/R2PeG+qBpE0KlN4sVufwBfe0GR+OdgSAizNo6hiIhzlkBSrdPDs 10 | qQegZGho3DCFa7ZZBoAXIfbbsHmD4AOnoRqGU00nRjMkVO+A8x5jovmJFPuJ6SlG 11 | 3ABzvzLbmMzf3QSp23NPcFKeFZy9M5kycdfxN9N6MK9Wf49JqnFLfdQfGb5p7vA3 12 | 70AsyXGesejpXJaPr5vI4clrRGwl8wnKUST4ppuLMvsQ1DCHeno6i41eKOsWD0U5 13 | 2/kh/9GWi5pGeiqz6d9WgMBSX5TPc92DVtqNyybj2QKBgQDwdLiisZDTGoDTNH6u 14 | LShEr6dKIGpxwCiSvChR2EvOuQMc4jprfDDkoFpxdtwCwLH29SMtMbd+PhByeq/l 15 | vmtYsWX+bdgzt6VLdl2t5Ryq9kaWkQhp+/Oo8it2qMqWzppHAkPg4OHghUv8HgYN 16 | us+KDzN/K4F9uCKTNzsS9by8zwKBgQDs+jJ1i/cFoA66w3vXQLKW4B82FisDQ+r1 17 | x7TGTVchXfkh38Uv6ub7J7s1046A3AjJmQWvzIkEIPqpR+Zg2edw7hmmbE7mhnVE 18 | omtnpcH+Wt22SaeSgr2k5ATZecGui0XoVWmt2/2foIjN0qB+4uDhu7CdhdI1pqJg 19 | SeZ7t1xP+wKBgGdWckcRMqFW4YhjJhIqvASQzL9RoQ1TvNwa2uUZpLKp1kmie+LX 20 | k5edxmC+6RyRvanZfg2dMTOi7qSUT2XX6QBL9P5cLK9FPFdq/iWOsjxS7NEmcUxD 21 | CFtSABXVwoL/NVRRB+TsIIbCIn6uVIGwCCNYFxSPGO5zpQBJmyyxyTQHAoGALo8J 22 | kuao5cYIntEBZ80iSVpHR40bbkzNwPQ3pC5VzAx2gh1A1KVGNFoeAflKqi6Q22rh 23 | HyUEVndUSCYEMUsemaVuDLCC3+/S4mU2zOpYLu0TWlkmPzbIo7jY6xFs6Coc1gTR 24 | +oKaVd5ogme7eLKClFcX4BsGrdvLUzexvOTNkjkCgYEAwVtBvzXFiMnMxkR+C2BS 25 | 4rNKMnont0bJeAU53zoY1ufhLkzkSz7cD+2+iiO1wgsszQbyzHs/fcbipPW5Oy74 26 | LWnbqhhDUIQBPZPASyO96Y8EY6XH5r1hwN88Ag8lVurx/0QwGxJ6rKJ6MpdDMiGl 27 | /zBimjrjTxyT+8NDMy7y4Eo= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /tests/keys/key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3pabl39yeTsg14HyVQpB 3 | r0Ub+WLkUF5CVxTa6//rSJV2HwZ1XL8uPD1aom37dh4yar7+k5RK1RZ+BrDNTpYp 4 | 78IkFTpedJVrqxuhsofoZHuW4F9o+7a6YnV0y7m8yZl3lJlGR9tPGMGqfgMvK+08 5 | yH1HnnaLi22iHw/S/J+YBaSJCLcwI6dg1JHWoy/wueY8+KLcmUzzxBWMgIUizJsf 6 | XpT9ZzmjTazXIoUKZvCivubL1fRCGaCOdVRrjreJpCWmyF2s2+ZkRaL1sDwASGex 7 | atnb5D/nSGcDe5d9EUxanDIZct2SUibS/dUWYZT90o0CbMMpC72jkelFM7GtAbT/ 8 | 9QIDAQAB 9 | -----END PUBLIC KEY----- 10 | 11 | -------------------------------------------------------------------------------- /tests/testdata/sample_operator_cfg.yaml: -------------------------------------------------------------------------------- 1 | # handler for adapter appidentityandaccessadapter 2 | apiVersion: "config.istio.io/v1alpha2" 3 | kind: handler 4 | metadata: 5 | name: handler-appidentityandaccessadapter 6 | namespace: istio-system 7 | spec: 8 | adapter: appidentityandaccessadapter 9 | connection: 10 | address: "[::]:47304" 11 | --- 12 | # instance for authorization template 13 | apiVersion: "config.istio.io/v1alpha2" 14 | kind: instance 15 | metadata: 16 | name: instance-appidentityandaccessadapter 17 | namespace: istio-system 18 | spec: 19 | template: authnz 20 | params: 21 | target: 22 | path: request.url_path | "/" 23 | method: request.method | "get" 24 | service: destination.service.name | "" 25 | namespace: destination.service.namespace | "default" 26 | request: 27 | scheme: request.scheme | "" 28 | host: request.host | "" 29 | path: request.url_path | "" 30 | headers: 31 | cookies: request.headers["cookie"] | "" 32 | authorization: request.headers["authorization"] | "" 33 | params: 34 | code: request.query_params["code"] | "" 35 | error: request.query_params["error"] | "" 36 | state: request.query_params["state"] | "" 37 | --- 38 | # rule to dispatch the app ID adapter handler 39 | apiVersion: "config.istio.io/v1alpha2" 40 | kind: rule 41 | metadata: 42 | name: rule-appidentityandaccessadapter 43 | namespace: istio-system 44 | spec: 45 | actions: 46 | - handler: handler-appidentityandaccessadapter.istio-system 47 | instances: 48 | - instance-appidentityandaccessadapter 49 | name: a1 50 | requestHeaderOperations: 51 | - name: Authorization 52 | values: [ a1.output.authorization ] 53 | operation: REPLACE 54 | --- 55 | --------------------------------------------------------------------------------