diff --git a/CHANGELOG.md b/CHANGELOG.md index b2f0a86..f908aad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +### Removed +- Remove all usage of Conjur v4 + [cyberark/conjur-api-go#139](https://github.com/cyberark/conjur-api-go/pull/139) + ## [0.10.2] - 2022-11-14 ### Fixed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7275607..e038df0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -43,17 +43,6 @@ and will run the tests in a `golang:1.17` container Supported arguments are `1.17` and `1.18`, with the default being `1.17` if no argument is given. -To run just the tests against just the Conjur Open Source, run: - -```shell -export TEST_VERSION="oss" -# This will spin up a containerized Conjur oss -./bin/test.sh -``` - -Possible values for `TEST_VERSION` are `oss` and `all`, with `all` -being the default. - ### Setting up a development environment To start a container with terminal access, and the necessary test running dependencies installed, run: diff --git a/bin/dev.sh b/bin/dev.sh index a0569a2..0e0a0a8 100755 --- a/bin/dev.sh +++ b/bin/dev.sh @@ -16,6 +16,4 @@ exec_on dev go mod download # Start interactive container docker exec -it \ -e CONJUR_AUTHN_API_KEY \ - -e CONJUR_V4_AUTHN_API_KEY \ - -e CONJUR_V4_SSL_CERTIFICATE \ "$(docker-compose ps -q dev)" /bin/bash diff --git a/bin/start-conjur.sh b/bin/start-conjur.sh index 7d0e803..38c8ada 100755 --- a/bin/start-conjur.sh +++ b/bin/start-conjur.sh @@ -4,92 +4,31 @@ trap teardown EXIT -# Type of Conjur to test against, 'all' or 'oss' -export TEST_VERSION="${TEST_VERSION:-all}" -announce "Compose Project Name: $COMPOSE_PROJECT_NAME - Conjur Test Version: $TEST_VERSION" +announce "Compose Project Name: $COMPOSE_PROJECT_NAME" main() { - # If oss only, we don't run v4 tests - if oss_only; then - images=("conjur") - else - images=("conjur" "cuke-master" ) - fi - announce "Pulling images..." - docker-compose pull ${images[@]} "postgres" "cli5" + docker-compose pull "conjur" "postgres" "cli5" echo "Done!" announce "Building images..." - docker-compose build ${images[@]} "postgres" + docker-compose build "conjur" "postgres" echo "Done!" announce "Starting Conjur environment..." export CONJUR_DATA_KEY="$(docker-compose run -T --no-deps conjur data-key generate)" - docker-compose up --no-deps -d ${images[@]} "postgres" + docker-compose up --no-deps -d "conjur" "postgres" echo "Done!" announce "Waiting for conjur to start..." exec_on conjur conjurctl wait - if ! oss_only; then - exec_on cuke-master /opt/conjur/evoke/bin/wait_for_conjur - fi + echo "Done!" api_key=$(exec_on conjur conjurctl role retrieve-key cucumber:user:admin | tr -d '\r') - if ! oss_only; then - announce "Running cuke setup..." - exec_on cuke-master bash -c 'conjur authn login -u admin -p secret' - exec_on cuke-master conjur user create --as-group security_admin alice - exec_on cuke-master conjur host create --as-group security_admin bob - exec_on cuke-master conjur variable create existent-variable-with-undefined-value - - # These variables will be checked for during the go testing - # For example, see conjurapi/variable_test.go - vars=( - 'existent-variable-with-defined-value' - 'a/ b/c' - 'myapp-01' - 'alice@devops' - 'prod/aws/db-password' - 'research+development' - 'sales&marketing' - 'onemore' - 'binary' - ) - - secrets=( - 'existent-variable-defined-value' - 'a/ b/c' - 'these' - 'are' - 'all' - 'secret' - 'strings' - '{"json": "object"}' - "$(openssl rand 10)" - ) - - count=${#vars[@]} - for ((i=0; i<$count; i++)); do - id="${vars[$i]}" - val="${secrets[$i]}" - exec_on cuke-master conjur variable create "$id" - exec_on cuke-master conjur variable values add "$id" "$val" - done - - api_key_v4=$(exec_on cuke-master conjur user rotate_api_key) - ssl_cert_v4=$(exec_on cuke-master cat /opt/conjur/etc/ssl/ca.pem) - - echo "Done!" - fi - # Export values needed for tests to access Conjur instance export CONJUR_AUTHN_API_KEY="$api_key" - export CONJUR_V4_AUTHN_API_KEY="$api_key_v4" - export CONJUR_V4_SSL_CERTIFICATE="$ssl_cert_v4" } main diff --git a/bin/test.sh b/bin/test.sh index c498d7b..e250d2f 100755 --- a/bin/test.sh +++ b/bin/test.sh @@ -19,7 +19,6 @@ mkdir -p $output_dir failed() { announce "TESTS FAILED" - docker logs "$(docker-compose ps -q cuke-master)" exit 1 } @@ -27,8 +26,6 @@ failed() { announce "Running tests for Go version: $GO_VERSION..."; docker-compose run \ -e CONJUR_AUTHN_API_KEY \ - -e CONJUR_V4_AUTHN_API_KEY \ - -e CONJUR_V4_SSL_CERTIFICATE \ -e GO_VERSION \ "test-$GO_VERSION" bash -c 'set -o pipefail; echo "Go version: $(go version)" diff --git a/bin/utils.sh b/bin/utils.sh index 955d59a..dc3d465 100755 --- a/bin/utils.sh +++ b/bin/utils.sh @@ -3,13 +3,11 @@ export compose_file="../docker-compose.yml" function announce() { - BLUE='\033[0;34m' - NC='\033[0m' # No Color - echo -e "$BLUE + echo " ================================ ${1} ================================ - $NC" + " } exec_on() { @@ -18,10 +16,6 @@ exec_on() { docker exec "$(docker-compose ps -q $container)" "$@" } -oss_only(){ - [ "$TEST_VERSION" == "oss" ] -} - function teardown { docker-compose down -v } diff --git a/conjurapi/authn.go b/conjurapi/authn.go index 73ca6e9..79b84e7 100644 --- a/conjurapi/authn.go +++ b/conjurapi/authn.go @@ -11,7 +11,7 @@ import ( ) func (c *Client) RefreshToken() (err error) { - var token authn.AuthnToken + var token *authn.AuthnToken if c.NeedsTokenRefresh() { var tokenBytes []byte @@ -69,7 +69,7 @@ func (c *Client) AuthenticateReader(loginPair authn.LoginPair) (io.ReadCloser, e } func (c *Client) authenticate(loginPair authn.LoginPair) (*http.Response, error) { - req, err := c.router.AuthenticateRequest(loginPair) + req, err := c.AuthenticateRequest(loginPair) if err != nil { return nil, err } @@ -104,7 +104,7 @@ func (c *Client) RotateAPIKeyReader(roleID string) (io.ReadCloser, error) { } func (c *Client) rotateAPIKey(roleID string) (*http.Response, error) { - req, err := c.router.RotateAPIKeyRequest(roleID) + req, err := c.RotateAPIKeyRequest(roleID) if err != nil { return nil, err } diff --git a/conjurapi/authn/auth_token.go b/conjurapi/authn/auth_token.go index f4dbcd0..1bf6a90 100644 --- a/conjurapi/authn/auth_token.go +++ b/conjurapi/authn/auth_token.go @@ -11,27 +11,10 @@ const ( TimeFormatToken4 = "2006-01-02 15:04:05 MST" ) -type AuthnToken interface { - // Parse from JSON. Required before further usage. - FromJSON(data []byte) error - // Raw token as obtained from the authentication service. - Raw() []byte - // Whether the token will expire soon. - ShouldRefresh() bool -} - -type AuthnToken4 struct { - bytes []byte - Data string `json:"data"` - Signature string `json:"signature"` - Key string `json:"key"` - Timestamp time.Time -} - // Sample token // {"protected":"eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiI5M2VjNTEwODRmZTM3Zjc3M2I1ODhlNTYyYWVjZGMxMSJ9","payload":"eyJzdWIiOiJhZG1pbiIsImlhdCI6MTUxMDc1MzI1OX0=","signature":"raCufKOf7sKzciZInQTphu1mBbLhAdIJM72ChLB4m5wKWxFnNz_7LawQ9iYEI_we1-tdZtTXoopn_T1qoTplR9_Bo3KkpI5Hj3DB7SmBpR3CSRTnnEwkJ0_aJ8bql5Cbst4i4rSftyEmUqX-FDOqJdAztdi9BUJyLfbeKTW9OGg-QJQzPX1ucB7IpvTFCEjMoO8KUxZpbHj-KpwqAMZRooG4ULBkxp5nSfs-LN27JupU58oRgIfaWASaDmA98O2x6o88MFpxK_M0FeFGuDKewNGrRc8lCOtTQ9cULA080M5CSnruCqu1Qd52r72KIOAfyzNIiBCLTkblz2fZyEkdSKQmZ8J3AakxQE2jyHmMT-eXjfsEIzEt-IRPJIirI3Qm"} // https://www.conjur.org/reference/cryptography.html -type AuthnToken5 struct { +type AuthnToken struct { bytes []byte Protected string `json:"protected"` Payload string `json:"payload"` @@ -45,7 +28,7 @@ func hasField(fields map[string]string, name string) (hasField bool) { return } -func NewToken(data []byte) (token AuthnToken, err error) { +func NewToken(data []byte) (token *AuthnToken, err error) { fields := make(map[string]string) if err = json.Unmarshal(data, &fields); err != nil { err = fmt.Errorf("Unable to unmarshal token : %s", err) @@ -53,10 +36,7 @@ func NewToken(data []byte) (token AuthnToken, err error) { } if hasField(fields, "protected") && hasField(fields, "payload") && hasField(fields, "signature") { - t := &AuthnToken5{} - token = t - } else if hasField(fields, "data") && hasField(fields, "timestamp") && hasField(fields, "signature") && hasField(fields, "key") { - t := &AuthnToken4{} + t := &AuthnToken{} token = t } else { err = fmt.Errorf("Unrecognized token format") @@ -68,12 +48,12 @@ func NewToken(data []byte) (token AuthnToken, err error) { return } -func (t *AuthnToken5) FromJSON(data []byte) (err error) { +func (t *AuthnToken) FromJSON(data []byte) (err error) { t.bytes = data err = json.Unmarshal(data, &t) if err != nil { - err = fmt.Errorf("Unable to unmarshal v5 access token %s", err) + err = fmt.Errorf("Unable to unmarshal access token %s", err) return } @@ -82,18 +62,18 @@ func (t *AuthnToken5) FromJSON(data []byte) (err error) { var payloadJSON []byte payloadJSON, err = base64.StdEncoding.DecodeString(t.Payload) if err != nil { - err = fmt.Errorf("v5 access token field 'payload' is not valid base64") + err = fmt.Errorf("access token field 'payload' is not valid base64") return } err = json.Unmarshal(payloadJSON, &payloadFields) if err != nil { - err = fmt.Errorf("Unable to unmarshal v5 access token field 'payload' : %s", err) + err = fmt.Errorf("Unable to unmarshal access token field 'payload' : %s", err) return } iat_v, ok := payloadFields["iat"] if !ok { - err = fmt.Errorf("v5 access token field 'payload' does not contain 'iat'") + err = fmt.Errorf("access token field 'payload' does not contain 'iat'") return } iat_f := iat_v.(float64) @@ -106,7 +86,7 @@ func (t *AuthnToken5) FromJSON(data []byte) (err error) { exp := time.Unix(int64(exp_f), 0) t.exp = &exp if t.iat.After(*t.exp) { - err = fmt.Errorf("v5 access token expired before it was issued") + err = fmt.Errorf("access token expired before it was issued") return } } @@ -114,42 +94,11 @@ func (t *AuthnToken5) FromJSON(data []byte) (err error) { return } -func (t *AuthnToken4) FromJSON(data []byte) (err error) { - err = json.Unmarshal(data, &t) - if err != nil { - err = fmt.Errorf("Unable to unmarshal v4 access token %s", err) - } - - return -} - -func (t *AuthnToken4) UnmarshalJSON(data []byte) (err error) { - type Alias AuthnToken4 - x := &struct { - Data string `json:"data"` - Timestamp string `json:"timestamp"` - Signature string `json:"signature"` - Key string `json:"key"` - *Alias - }{ - Alias: (*Alias)(t), - } - - if err = json.Unmarshal(data, &x); err != nil { - return - } - - t.Timestamp, err = time.Parse(TimeFormatToken4, x.Timestamp) - t.bytes = data - - return -} - -func (t *AuthnToken5) Raw() []byte { +func (t *AuthnToken) Raw() []byte { return t.bytes } -func (t *AuthnToken5) ShouldRefresh() bool { +func (t *AuthnToken) ShouldRefresh() bool { if t.exp != nil { // Expire when the token is 85% expired lifespan := t.exp.Sub(t.iat) @@ -160,11 +109,3 @@ func (t *AuthnToken5) ShouldRefresh() bool { return time.Now().After(t.iat.Add(5 * time.Minute)) } } - -func (t *AuthnToken4) Raw() []byte { - return t.bytes -} - -func (t *AuthnToken4) ShouldRefresh() bool { - return time.Now().After(t.Timestamp.Add(5 * time.Minute)) -} diff --git a/conjurapi/authn/auth_token_test.go b/conjurapi/authn/auth_token_test.go index 3563630..c9cdb57 100644 --- a/conjurapi/authn/auth_token_test.go +++ b/conjurapi/authn/auth_token_test.go @@ -1,7 +1,6 @@ package authn import ( - "fmt" "reflect" "testing" "time" @@ -9,18 +8,18 @@ import ( "github.com/stretchr/testify/assert" ) -func TestTokenV5_Parse(t *testing.T) { +func TestToken_Parse(t *testing.T) { token_s := `{"protected":"eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiI5M2VjNTEwODRmZTM3Zjc3M2I1ODhlNTYyYWVjZGMxMSJ9","payload":"eyJzdWIiOiJhZG1pbiIsImlhdCI6MTUxMDc1MzI1OX0=","signature":"raCufKOf7sKzciZInQTphu1mBbLhAdIJM72ChLB4m5wKWxFnNz_7LawQ9iYEI_we1-tdZtTXoopn_T1qoTplR9_Bo3KkpI5Hj3DB7SmBpR3CSRTnnEwkJ0_aJ8bql5Cbst4i4rSftyEmUqX-FDOqJdAztdi9BUJyLfbeKTW9OGg-QJQzPX1ucB7IpvTFCEjMoO8KUxZpbHj-KpwqAMZRooG4ULBkxp5nSfs-LN27JupU58oRgIfaWASaDmA98O2x6o88MFpxK_M0FeFGuDKewNGrRc8lCOtTQ9cULA080M5CSnruCqu1Qd52r72KIOAfyzNIiBCLTkblz2fZyEkdSKQmZ8J3AakxQE2jyHmMT-eXjfsEIzEt-IRPJIirI3Qm"}` token_with_exp_s := `{"protected":"eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiI5M2VjNTEwODRmZTM3Zjc3M2I1ODhlNTYyYWVjZGMxMSJ9","payload":"eyJzdWIiOiJhZG1pbiIsImlhdCI6MTUxMDc1MzI1OSwiZXhwIjoxNTEwNzUzMzU5fQo=","signature":"raCufKOf7sKzciZInQTphu1mBbLhAdIJM72ChLB4m5wKWxFnNz_7LawQ9iYEI_we1-tdZtTXoopn_T1qoTplR9_Bo3KkpI5Hj3DB7SmBpR3CSRTnnEwkJ0_aJ8bql5Cbst4i4rSftyEmUqX-FDOqJdAztdi9BUJyLfbeKTW9OGg-QJQzPX1ucB7IpvTFCEjMoO8KUxZpbHj-KpwqAMZRooG4ULBkxp5nSfs-LN27JupU58oRgIfaWASaDmA98O2x6o88MFpxK_M0FeFGuDKewNGrRc8lCOtTQ9cULA080M5CSnruCqu1Qd52r72KIOAfyzNIiBCLTkblz2fZyEkdSKQmZ8J3AakxQE2jyHmMT-eXjfsEIzEt-IRPJIirI3Qm"}` token_mangled_s := `{"protected":"eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiI5M2VjNTEwODRmZTM3Zjc3M2I1ODhlNTYyYWVjZGMxMSJ9","payload":"WIiOiJhZG1","signature":"raCufKOf7sKzciZInQTphu1mBbLhAdIJM72ChLB4m5wKWxFnNz_7LawQ9iYEI_we1-tdZtTXoopn_T1qoTplR9_Bo3KkpI5Hj3DB7SmBpR3CSRTnnEwkJ0_aJ8bql5Cbst4i4rSftyEmUqX-FDOqJdAztdi9BUJyLfbeKTW9OGg-QJQzPX1ucB7IpvTFCEjMoO8KUxZpbHj-KpwqAMZRooG4ULBkxp5nSfs-LN27JupU58oRgIfaWASaDmA98O2x6o88MFpxK_M0FeFGuDKewNGrRc8lCOtTQ9cULA080M5CSnruCqu1Qd52r72KIOAfyzNIiBCLTkblz2fZyEkdSKQmZ8J3AakxQE2jyHmMT-eXjfsEIzEt-IRPJIirI3Qm"}` token_mangled_2_s := `{"protected":"eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiI5M2VjNTEwODRmZTM3Zjc3M2I1ODhlNTYyYWVjZGMxMSJ9","payload":"Zm9vYmFyCg==","signature":"raCufKOf7sKzciZInQTphu1mBbLhAdIJM72ChLB4m5wKWxFnNz_7LawQ9iYEI_we1-tdZtTXoopn_T1qoTplR9_Bo3KkpI5Hj3DB7SmBpR3CSRTnnEwkJ0_aJ8bql5Cbst4i4rSftyEmUqX-FDOqJdAztdi9BUJyLfbeKTW9OGg-QJQzPX1ucB7IpvTFCEjMoO8KUxZpbHj-KpwqAMZRooG4ULBkxp5nSfs-LN27JupU58oRgIfaWASaDmA98O2x6o88MFpxK_M0FeFGuDKewNGrRc8lCOtTQ9cULA080M5CSnruCqu1Qd52r72KIOAfyzNIiBCLTkblz2fZyEkdSKQmZ8J3AakxQE2jyHmMT-eXjfsEIzEt-IRPJIirI3Qm"}` - t.Run("Token type V5 is detected", func(t *testing.T) { + t.Run("Token is parsed successfully", func(t *testing.T) { token, err := NewToken([]byte(token_s)) assert.NoError(t, err) - assert.Equal(t, "*authn.AuthnToken5", reflect.TypeOf(token).String()) + assert.Equal(t, "*authn.AuthnToken", reflect.TypeOf(token).String()) assert.NotNil(t, token.Raw()) }) @@ -30,9 +29,8 @@ func TestTokenV5_Parse(t *testing.T) { assert.Equal(t, token_s, string(token.Raw())) - token_v5 := token.(*AuthnToken5) - assert.Equal(t, time.Unix(1510753259, 0).String(), token_v5.iat.String()) - assert.Nil(t, token_v5.exp) + assert.Equal(t, time.Unix(1510753259, 0).String(), token.iat.String()) + assert.Nil(t, token.exp) assert.True(t, token.ShouldRefresh()) }) @@ -41,58 +39,19 @@ func TestTokenV5_Parse(t *testing.T) { token, err := NewToken([]byte(token_with_exp_s)) assert.NoError(t, err) - token_v5 := token.(*AuthnToken5) - assert.Equal(t, time.Unix(1510753259, 0).String(), token_v5.iat.String()) - assert.Equal(t, time.Unix(1510753359, 0).String(), token_v5.exp.String()) + assert.Equal(t, time.Unix(1510753259, 0).String(), token.iat.String()) + assert.Equal(t, time.Unix(1510753359, 0).String(), token.exp.String()) assert.True(t, token.ShouldRefresh()) }) t.Run("Malformed base64 in token is reported", func(t *testing.T) { _, err := NewToken([]byte(token_mangled_s)) - assert.Equal(t, "v5 access token field 'payload' is not valid base64", err.Error()) + assert.Equal(t, "access token field 'payload' is not valid base64", err.Error()) }) t.Run("Malformed JSON in token is reported", func(t *testing.T) { _, err := NewToken([]byte(token_mangled_2_s)) - assert.Equal(t, "Unable to unmarshal v5 access token field 'payload' : invalid character 'o' in literal false (expecting 'a')", err.Error()) - }) -} - -func TestTokenV4_Parse(t *testing.T) { - expired_token_bytes := []byte(`{"data":"admin","timestamp":"2018-04-06 03:10:08 UTC","signature":"QxTMoWWYXbgMo_JuX4KHQuiPwPRe8fpIlnZMhlvHalyhJHK0RbkqOyw28ImLwClBaTPjx6KU7KmqYLi9pMszHQZhQ7A2fLm1v-x0XzZGrDOt6gd0fTEZ0CJl7VVxVBZWLrJ83r8tY-sdjKysrE1fyDXyMU_vDtgJVi9y72qddkH-Pl16Pd4PJceEEybfWylIs1Z5V5qn-ocWX18D-i9pB67Usz3m-wKa43TptiDYLGU1-Y_EXyilv_uNGouqwYa0IueK5yJxO1Rcyb2aCBG0i-0Vl7qYrT0zIwDqmxLAwbqOtrtfHngFOCqsW04jJLPOruR5FwMlGw90GT1lZH_3GCm6QK8p15IWfVS9UOky8Y4l-1vfh-d15BZPGemUbu0j","key":"86ffd9d612ad06fe978b559fbeba4ca2"}`) - - nextYear := time.Now().Year() + 1 - new_token_bytes := []byte(fmt.Sprintf(`{"data":"admin","timestamp":"%v-04-06 03:10:08 UTC","signature":"QxTMoWWYXbgMo_JuX4KHQuiPwPRe8fpIlnZMhlvHalyhJHK0RbkqOyw28ImLwClBaTPjx6KU7KmqYLi9pMszHQZhQ7A2fLm1v-x0XzZGrDOt6gd0fTEZ0CJl7VVxVBZWLrJ83r8tY-sdjKysrE1fyDXyMU_vDtgJVi9y72qddkH-Pl16Pd4PJceEEybfWylIs1Z5V5qn-ocWX18D-i9pB67Usz3m-wKa43TptiDYLGU1-Y_EXyilv_uNGouqwYa0IueK5yJxO1Rcyb2aCBG0i-0Vl7qYrT0zIwDqmxLAwbqOtrtfHngFOCqsW04jJLPOruR5FwMlGw90GT1lZH_3GCm6QK8p15IWfVS9UOky8Y4l-1vfh-d15BZPGemUbu0j","key":"86ffd9d612ad06fe978b559fbeba4ca2"}`, nextYear)) - - var expired_token *AuthnToken4 - - t.Run("Token type V4 is detected", func(t *testing.T) { - token, err := NewToken(expired_token_bytes) - - assert.NoError(t, err) - assert.Equal(t, "*authn.AuthnToken4", reflect.TypeOf(token).String()) - assert.NotNil(t, token.Raw()) - - expired_token, _ = token.(*AuthnToken4) - }) - - t.Run("Token timestamp is non-zero", func(t *testing.T) { - assert.False(t, expired_token.Timestamp.IsZero()) - }) - - t.Run("Expired token should be refreshed", func(t *testing.T) { - assert.True(t, expired_token.ShouldRefresh()) - }) - - t.Run("New token can be parsed and fields are valid", func(t *testing.T) { - token, err := NewToken([]byte(new_token_bytes)) - token4, _ := token.(*AuthnToken4) - - assert.NoError(t, err) - assert.Equal(t, "*authn.AuthnToken4", reflect.TypeOf(token).String()) - assert.False(t, token4.Timestamp.IsZero()) - assert.False(t, token4.ShouldRefresh()) - assert.NotNil(t, token4.Raw()) + assert.Equal(t, "Unable to unmarshal access token field 'payload' : invalid character 'o' in literal false (expecting 'a')", err.Error()) }) } diff --git a/conjurapi/authn_test.go b/conjurapi/authn_test.go index 1d42a0d..979234f 100644 --- a/conjurapi/authn_test.go +++ b/conjurapi/authn_test.go @@ -1,7 +1,6 @@ package conjurapi import ( - "os" "testing" "github.com/cyberark/conjur-api-go/conjurapi/authn" @@ -37,34 +36,14 @@ func TestClient_RotateAPIKey(t *testing.T) { }, } - t.Run("V5", func(t *testing.T) { + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // SETUP + conjur, err := conjurSetup() + assert.NoError(t, err) - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // SETUP - conjur, err := v5Setup() - assert.NoError(t, err) - - // EXERCISE - runAssertions(t, tc, conjur) - }) - } - }) - - if os.Getenv("TEST_VERSION") != "oss" { - t.Run("V4", func(t *testing.T) { - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - // SETUP - conjur, err := v4Setup() - assert.NoError(t, err) - - // EXERCISE - runAssertions(t, tc, conjur) - }) - - } + // EXERCISE + runAssertions(t, tc, conjur) }) } } @@ -72,6 +51,7 @@ func TestClient_RotateAPIKey(t *testing.T) { func runAssertions(t *testing.T, tc testCase, conjur *Client) { var userApiKey []byte var err error + if tc.readResponseBody { rotateResponse, e := conjur.RotateAPIKeyReader("cucumber:user:alice") assert.NoError(t, e) diff --git a/conjurapi/client.go b/conjurapi/client.go index 735290a..1f58600 100644 --- a/conjurapi/client.go +++ b/conjurapi/client.go @@ -10,6 +10,7 @@ import ( "net/url" "os" "strings" + "strconv" "time" "github.com/bgentry/go-netrc/netrc" @@ -24,22 +25,9 @@ type Authenticator interface { type Client struct { config Config - authToken authn.AuthnToken + authToken *authn.AuthnToken httpClient *http.Client authenticator Authenticator - router Router -} - -type Router interface { - AddSecretRequest(variableID, secretValue string) (*http.Request, error) - AuthenticateRequest(loginPair authn.LoginPair) (*http.Request, error) - CheckPermissionRequest(resourceID, privilege string) (*http.Request, error) - LoadPolicyRequest(mode PolicyMode, policyID string, policy io.Reader) (*http.Request, error) - ResourceRequest(resourceID string) (*http.Request, error) - ResourcesRequest(filter *ResourceFilter) (*http.Request, error) - RetrieveBatchSecretsRequest(variableIDs []string, base64Flag bool) (*http.Request, error) - RetrieveSecretRequest(variableID string) (*http.Request, error) - RotateAPIKeyRequest(roleID string) (*http.Request, error) } func NewClientFromKey(config Config, loginPair authn.LoginPair) (*Client, error) { @@ -216,6 +204,215 @@ func (c *Client) SubmitRequest(req *http.Request) (resp *http.Response, err erro return } +func (c *Client) AuthenticateRequest(loginPair authn.LoginPair) (*http.Request, error) { + authenticateURL := makeRouterURL(c.authnURL(), url.QueryEscape(loginPair.Login), "authenticate").String() + + req, err := http.NewRequest("POST", authenticateURL, strings.NewReader(loginPair.APIKey)) + if err != nil { + return nil, err + } + req.Header.Set("Content-Type", "text/plain") + + return req, nil +} + +func (c *Client) RotateAPIKeyRequest(roleID string) (*http.Request, error) { + account, _, _, err := parseID(roleID) + if err != nil { + return nil, err + } + if account != c.config.Account { + return nil, fmt.Errorf("Account of '%s' must match the configured account '%s'", roleID, c.config.Account) + } + + rotateURL := makeRouterURL(c.authnURL(), "api_key").withFormattedQuery("role=%s", roleID).String() + + return http.NewRequest( + "PUT", + rotateURL, + nil, + ) +} + +func (c *Client) CheckPermissionRequest(resourceID string, privilege string) (*http.Request, error) { + account, kind, id, err := parseID(resourceID) + if err != nil { + return nil, err + } + checkURL := makeRouterURL(c.resourcesURL(account), kind, url.QueryEscape(id)).withFormattedQuery("check=true&privilege=%s", url.QueryEscape(privilege)).String() + + return http.NewRequest( + "GET", + checkURL, + nil, + ) +} + +func (c *Client) ResourceRequest(resourceID string) (*http.Request, error) { + account, kind, id, err := parseID(resourceID) + if err != nil { + return nil, err + } + + requestURL := makeRouterURL(c.resourcesURL(account), kind, url.QueryEscape(id)) + + return http.NewRequest( + "GET", + requestURL.String(), + nil, + ) +} + +func (c *Client) ResourcesRequest(filter *ResourceFilter) (*http.Request, error) { + query := url.Values{} + + if filter != nil { + if filter.Kind != "" { + query.Add("kind", filter.Kind) + } + if filter.Search != "" { + query.Add("search", filter.Search) + } + + if filter.Limit != 0 { + query.Add("limit", strconv.Itoa(filter.Limit)) + } + + if filter.Offset != 0 { + query.Add("offset", strconv.Itoa(filter.Offset)) + } + } + + requestURL := makeRouterURL(c.resourcesURL(c.config.Account)).withQuery(query.Encode()) + + return http.NewRequest( + "GET", + requestURL.String(), + nil, + ) +} + +func (c *Client) LoadPolicyRequest(mode PolicyMode, policyID string, policy io.Reader) (*http.Request, error) { + fullPolicyID := makeFullId(c.config.Account, "policy", policyID) + + account, kind, id, err := parseID(fullPolicyID) + if err != nil { + return nil, err + } + policyURL := makeRouterURL(c.policiesURL(account), kind, url.QueryEscape(id)).String() + + var method string + switch mode { + case PolicyModePost: + method = "POST" + case PolicyModePatch: + method = "PATCH" + case PolicyModePut: + method = "PUT" + default: + return nil, fmt.Errorf("Invalid PolicyMode : %d", mode) + } + + return http.NewRequest( + method, + policyURL, + policy, + ) +} + +func (c *Client) RetrieveBatchSecretsRequest(variableIDs []string, base64Flag bool) (*http.Request, error) { + fullVariableIDs := []string{} + for _, variableID := range variableIDs { + fullVariableID := makeFullId(c.config.Account, "variable", variableID) + fullVariableIDs = append(fullVariableIDs, fullVariableID) + } + + request, err := http.NewRequest( + "GET", + c.batchVariableURL(fullVariableIDs), + nil, + ) + + if err != nil { + return nil, err + } + + if base64Flag { + request.Header.Add("Accept-Encoding", "base64") + } + + return request, nil +} + +func (c *Client) RetrieveSecretRequest(variableID string) (*http.Request, error) { + fullVariableID := makeFullId(c.config.Account, "variable", variableID) + + variableURL, err := c.variableURL(fullVariableID) + if err != nil { + return nil, err + } + + return http.NewRequest( + "GET", + variableURL, + nil, + ) +} + +func (c *Client) AddSecretRequest(variableID, secretValue string) (*http.Request, error) { + fullVariableID := makeFullId(c.config.Account, "variable", variableID) + + variableURL, err := c.variableURL(fullVariableID) + if err != nil { + return nil, err + } + + request, err := http.NewRequest( + "POST", + variableURL, + strings.NewReader(secretValue), + ) + if err != nil { + return nil, err + } + + request.Header.Add("Content-Type", "application/x-www-form-urlencoded") + return request, nil +} + +func (c *Client) variableURL(variableID string) (string, error) { + account, kind, id, err := parseID(variableID) + if err != nil { + return "", err + } + return makeRouterURL(c.secretsURL(account), kind, url.PathEscape(id)).String(), nil +} + +func (c *Client) batchVariableURL(variableIDs []string) string { + queryString := url.QueryEscape(strings.Join(variableIDs, ",")) + return makeRouterURL(c.globalSecretsURL()).withFormattedQuery("variable_ids=%s", queryString).String() +} + +func (c *Client) authnURL() string { + return makeRouterURL(c.config.ApplianceURL, "authn", c.config.Account).String() +} + +func (c *Client) resourcesURL(account string) string { + return makeRouterURL(c.config.ApplianceURL, "resources", account).String() +} + +func (c *Client) secretsURL(account string) string { + return makeRouterURL(c.config.ApplianceURL, "secrets", account).String() +} + +func (c *Client) globalSecretsURL() string { + return makeRouterURL(c.config.ApplianceURL, "secrets").String() +} + +func (c *Client) policiesURL(account string) string { + return makeRouterURL(c.config.ApplianceURL, "policies", account).String() +} + func makeFullId(account, kind, id string) string { tokens := strings.SplitN(id, ":", 3) switch len(tokens) { @@ -248,7 +445,6 @@ func newClientWithAuthenticator(config Config, authenticator Authenticator) (*Cl } var httpClient *http.Client - var router Router if config.IsHttps() { cert, err := config.ReadSSLCert() @@ -263,17 +459,10 @@ func newClientWithAuthenticator(config Config, authenticator Authenticator) (*Cl httpClient = &http.Client{Timeout: time.Second * 10} } - if config.V4 { - router = RouterV4{&config} - } else { - router = RouterV5{&config} - } - return &Client{ config: config, authenticator: authenticator, httpClient: httpClient, - router: router, }, nil } diff --git a/conjurapi/client_test.go b/conjurapi/client_test.go index e1719f5..b6e7f2f 100644 --- a/conjurapi/client_test.go +++ b/conjurapi/client_test.go @@ -27,7 +27,6 @@ func TestClient_GetConfig(t *testing.T) { NetRCPath: "some-netrc-path", SSLCert: "some-ssl-cert", SSLCertPath: "some-ssl-cert-path", - V4: true, } client := Client{ config: expectedConfig, diff --git a/conjurapi/config.go b/conjurapi/config.go index 6050f9f..c1a45ac 100644 --- a/conjurapi/config.go +++ b/conjurapi/config.go @@ -20,7 +20,6 @@ type Config struct { NetRCPath string `yaml:"netrc_path,omitempty"` SSLCert string `yaml:"-"` SSLCertPath string `yaml:"cert_file,omitempty"` - V4 bool `yaml:"v4"` } func (c *Config) IsHttps() bool { @@ -78,7 +77,6 @@ func (c *Config) merge(o *Config) { c.SSLCert = mergeValue(c.SSLCert, o.SSLCert) c.SSLCertPath = mergeValue(c.SSLCertPath, o.SSLCertPath) c.NetRCPath = mergeValue(c.NetRCPath, o.NetRCPath) - c.V4 = c.V4 || o.V4 } func (c *Config) mergeYAML(filename string) error { @@ -99,7 +97,6 @@ func (c *Config) mergeYAML(filename string) error { logging.ApiLog.Errorf("Parsing error %s: %s\n", filename, err) return err } - aux.Config.V4 = aux.ConjurVersion == "4" logging.ApiLog.Debugf("Config from %s: %+v\n", filename, aux.Config) c.merge(&aux.Config) @@ -108,15 +105,12 @@ func (c *Config) mergeYAML(filename string) error { } func (c *Config) mergeEnv() { - majorVersion4 := os.Getenv("CONJUR_MAJOR_VERSION") == "4" || os.Getenv("CONJUR_VERSION") == "4" - env := Config{ ApplianceURL: os.Getenv("CONJUR_APPLIANCE_URL"), SSLCert: os.Getenv("CONJUR_SSL_CERTIFICATE"), SSLCertPath: os.Getenv("CONJUR_CERT_FILE"), Account: os.Getenv("CONJUR_ACCOUNT"), NetRCPath: os.Getenv("CONJUR_NETRC_PATH"), - V4: majorVersion4, } logging.ApiLog.Debugf("Config from environment: %+v\n", env) diff --git a/conjurapi/config_test.go b/conjurapi/config_test.go index f8ae578..1587b9a 100644 --- a/conjurapi/config_test.go +++ b/conjurapi/config_test.go @@ -147,7 +147,7 @@ netrc_path: "/path/to/netrc/file%v" defer os.Remove(tmpFileName) // clean up assert.NoError(t, err) - t.Run(fmt.Sprintf("Returns Config loaded with values from file and V4: %t", versiontest.out), func(t *testing.T) { + t.Run(fmt.Sprintf("Returns Config loaded with values from file: %t", versiontest.out), func(t *testing.T) { config := &Config{} config.mergeYAML(tmpFileName) @@ -156,7 +156,6 @@ netrc_path: "/path/to/netrc/file%v" ApplianceURL: fmt.Sprintf("http://path/to/appliance%v", index), NetRCPath: fmt.Sprintf("/path/to/netrc/file%v", index), SSLCertPath: fmt.Sprintf("/path/to/cert/file/pem%v", index), - V4: versiontest.out, }) }) }) diff --git a/conjurapi/policy.go b/conjurapi/policy.go index 9acc5cb..b5dfe8c 100644 --- a/conjurapi/policy.go +++ b/conjurapi/policy.go @@ -37,7 +37,7 @@ type PolicyResponse struct { // // The required permission depends on the mode. func (c *Client) LoadPolicy(mode PolicyMode, policyID string, policy io.Reader) (*PolicyResponse, error) { - req, err := c.router.LoadPolicyRequest(mode, policyID, policy) + req, err := c.LoadPolicyRequest(mode, policyID, policy) if err != nil { return nil, err } diff --git a/conjurapi/policy_test.go b/conjurapi/policy_test.go index ed93f7a..752e39f 100644 --- a/conjurapi/policy_test.go +++ b/conjurapi/policy_test.go @@ -14,109 +14,73 @@ import ( ) func TestClient_LoadPolicy(t *testing.T) { - t.Run("V5", func(t *testing.T) { - config := &Config{} - config.mergeEnv() + config := &Config{} + config.mergeEnv() - apiKey := os.Getenv("CONJUR_AUTHN_API_KEY") - login := os.Getenv("CONJUR_AUTHN_LOGIN") + apiKey := os.Getenv("CONJUR_AUTHN_API_KEY") + login := os.Getenv("CONJUR_AUTHN_LOGIN") - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + assert.NoError(t, err) - randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) + randomizer := rand.New(rand.NewSource(time.Now().UnixNano())) - t.Run("Successfully load policy", func(t *testing.T) { - username := "alice" - policy := fmt.Sprintf(` + t.Run("Successfully load policy", func(t *testing.T) { + username := "alice" + policy := fmt.Sprintf(` - !user %s `, username) - resp, err := conjur.LoadPolicy( - PolicyModePut, - "root", - strings.NewReader(policy), - ) + resp, err := conjur.LoadPolicy( + PolicyModePut, + "root", + strings.NewReader(policy), + ) - assert.NoError(t, err) - assert.GreaterOrEqual(t, resp.Version, uint32(1)) - }) + assert.NoError(t, err) + assert.GreaterOrEqual(t, resp.Version, uint32(1)) + }) - t.Run("A new role is reported in the policy load response", func(t *testing.T) { - const chars = "abcdefghijklmnopqrstuvwxyz0123456789" - result := make([]byte, 12) - for i := range result { - result[i] = chars[randomizer.Intn(len(chars))] - } + t.Run("A new role is reported in the policy load response", func(t *testing.T) { + const chars = "abcdefghijklmnopqrstuvwxyz0123456789" + result := make([]byte, 12) + for i := range result { + result[i] = chars[randomizer.Intn(len(chars))] + } - username := string(result) - policy := fmt.Sprintf(` + username := string(result) + policy := fmt.Sprintf(` - !user %s `, username) - resp, err := conjur.LoadPolicy( - PolicyModePut, - "root", - strings.NewReader(policy), - ) - - assert.NoError(t, err) - createdRole, ok := resp.CreatedRoles["cucumber:user:"+username] - assert.NotEmpty(t, createdRole.ID) - assert.NotEmpty(t, createdRole.APIKey) - assert.True(t, ok) - }) - - t.Run("Given invalid login credentials", func(t *testing.T) { - login = "invalid-user" - - t.Run("Returns 401", func(t *testing.T) { - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + resp, err := conjur.LoadPolicy( + PolicyModePut, + "root", + strings.NewReader(policy), + ) - resp, err := conjur.LoadPolicy(PolicyModePut, "root", strings.NewReader("")) - - assert.Error(t, err) - assert.Nil(t, resp) - assert.IsType(t, &response.ConjurError{}, err) - conjurError := err.(*response.ConjurError) - assert.Equal(t, 401, conjurError.Code) - }) - - }) + assert.NoError(t, err) + createdRole, ok := resp.CreatedRoles["cucumber:user:"+username] + assert.NotEmpty(t, createdRole.ID) + assert.NotEmpty(t, createdRole.APIKey) + assert.True(t, ok) }) - if os.Getenv("TEST_VERSION") != "oss" { - t.Run("V4", func(t *testing.T) { - - config := &Config{ - ApplianceURL: os.Getenv("CONJUR_V4_APPLIANCE_URL"), - SSLCert: os.Getenv("CONJUR_V4_SSL_CERTIFICATE"), - Account: os.Getenv("CONJUR_V4_ACCOUNT"), - V4: true, - } - - login := os.Getenv("CONJUR_V4_AUTHN_LOGIN") - apiKey := os.Getenv("CONJUR_V4_AUTHN_API_KEY") + t.Run("Given invalid login credentials", func(t *testing.T) { + login = "invalid-user" + t.Run("Returns 401", func(t *testing.T) { conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) assert.NoError(t, err) - t.Run("Policy loading is not supported", func(t *testing.T) { - variableIdentifier := "alice" - policy := fmt.Sprintf(` -- !user %s -`, variableIdentifier) - - _, err = conjur.LoadPolicy( - PolicyModePut, - "root", - strings.NewReader(policy), - ) + resp, err := conjur.LoadPolicy(PolicyModePut, "root", strings.NewReader("")) - assert.Error(t, err) - assert.Equal(t, "LoadPolicy is not supported for Conjur V4", err.Error()) - }) + assert.Error(t, err) + assert.Nil(t, resp) + assert.IsType(t, &response.ConjurError{}, err) + conjurError := err.(*response.ConjurError) + assert.Equal(t, 401, conjurError.Code) }) - } + + }) } diff --git a/conjurapi/resource.go b/conjurapi/resource.go index b1f9684..09721ed 100644 --- a/conjurapi/resource.go +++ b/conjurapi/resource.go @@ -17,7 +17,7 @@ type ResourceFilter struct { // CheckPermission determines whether the authenticated user has a specified privilege // on a resource. func (c *Client) CheckPermission(resourceID, privilege string) (bool, error) { - req, err := c.router.CheckPermissionRequest(resourceID, privilege) + req, err := c.CheckPermissionRequest(resourceID, privilege) if err != nil { return false, err } @@ -38,7 +38,7 @@ func (c *Client) CheckPermission(resourceID, privilege string) (bool, error) { // Resource fetches a single user-visible resource by id. func (c *Client) Resource(resourceID string) (resource map[string]interface{}, err error) { - req, err := c.router.ResourceRequest(resourceID) + req, err := c.ResourceRequest(resourceID) if err != nil { return } @@ -62,7 +62,7 @@ func (c *Client) Resource(resourceID string) (resource map[string]interface{}, e // be limited by the given ResourceFilter. If filter is non-nil, only // non-zero-valued members of the filter will be applied. func (c *Client) Resources(filter *ResourceFilter) (resources []map[string]interface{}, err error) { - req, err := c.router.ResourcesRequest(filter) + req, err := c.ResourcesRequest(filter) if err != nil { return } diff --git a/conjurapi/resource_test.go b/conjurapi/resource_test.go index e3a8f51..cfec97b 100644 --- a/conjurapi/resource_test.go +++ b/conjurapi/resource_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/assert" ) -func v5Setup() (*Client, error) { +func conjurSetup() (*Client, error) { config := &Config{} config.mergeEnv() @@ -55,20 +55,6 @@ func v5Setup() (*Client, error) { return conjur, err } -func v4Setup() (*Client, error) { - config := &Config{ - ApplianceURL: os.Getenv("CONJUR_V4_APPLIANCE_URL"), - SSLCert: os.Getenv("CONJUR_V4_SSL_CERTIFICATE"), - Account: os.Getenv("CONJUR_V4_ACCOUNT"), - V4: true, - } - - login := os.Getenv("CONJUR_V4_AUTHN_LOGIN") - apiKey := os.Getenv("CONJUR_V4_AUTHN_API_KEY") - - return NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) -} - func TestClient_CheckPermission(t *testing.T) { checkAllowed := func(conjur *Client, id string) func(t *testing.T) { return func(t *testing.T) { @@ -88,25 +74,12 @@ func TestClient_CheckPermission(t *testing.T) { } } - t.Run("V5", func(t *testing.T) { - conjur, err := v5Setup() - assert.NoError(t, err) - - t.Run("Check an allowed permission", checkAllowed(conjur, "cucumber:variable:db-password")) - - t.Run("Check a permission on a non-existent resource", checkNonExisting(conjur, "cucumber:variable:foobar")) - }) - - if os.Getenv("TEST_VERSION") != "oss" { - t.Run("V4", func(t *testing.T) { - conjur, err := v4Setup() - assert.NoError(t, err) + conjur, err := conjurSetup() + assert.NoError(t, err) - t.Run("Check an allowed permission", checkAllowed(conjur, "cucumber:variable:existent-variable-with-defined-value")) + t.Run("Check an allowed permission", checkAllowed(conjur, "cucumber:variable:db-password")) - t.Run("Check a permission on a non-existent resource", checkNonExisting(conjur, "cucumber:variable:foobar")) - }) - } + t.Run("Check a permission on a non-existent resource", checkNonExisting(conjur, "cucumber:variable:foobar")) } func TestClient_Resources(t *testing.T) { @@ -118,26 +91,16 @@ func TestClient_Resources(t *testing.T) { } } - t.Run("V5", func(t *testing.T) { - conjur, err := v5Setup() - assert.NoError(t, err) - - t.Run("Lists all resources", listResources(conjur, nil, 12)) - t.Run("Lists resources by kind", listResources(conjur, &ResourceFilter{Kind: "variable"}, 7)) - t.Run("Lists resources that start with db", listResources(conjur, &ResourceFilter{Search: "db"}, 2)) - t.Run("Lists variables that start with prod/database", listResources(conjur, &ResourceFilter{Search: "prod/database", Kind: "variable"}, 2)) - t.Run("Lists variables that start with prod", listResources(conjur, &ResourceFilter{Search: "prod", Kind: "variable"}, 4)) - t.Run("Lists resources and limit result to 1", listResources(conjur, &ResourceFilter{Limit: 1}, 1)) - t.Run("Lists resources after the first", listResources(conjur, &ResourceFilter{Offset: 1}, 10)) - }) - - if os.Getenv("TEST_VERSION") != "oss" { - t.Run("V4", func(t *testing.T) { - _, err := v4Setup() - assert.NoError(t, err) - // v4 router doesn't support it showResource - }) - } + conjur, err := conjurSetup() + assert.NoError(t, err) + + t.Run("Lists all resources", listResources(conjur, nil, 12)) + t.Run("Lists resources by kind", listResources(conjur, &ResourceFilter{Kind: "variable"}, 7)) + t.Run("Lists resources that start with db", listResources(conjur, &ResourceFilter{Search: "db"}, 2)) + t.Run("Lists variables that start with prod/database", listResources(conjur, &ResourceFilter{Search: "prod/database", Kind: "variable"}, 2)) + t.Run("Lists variables that start with prod", listResources(conjur, &ResourceFilter{Search: "prod", Kind: "variable"}, 4)) + t.Run("Lists resources and limit result to 1", listResources(conjur, &ResourceFilter{Limit: 1}, 1)) + t.Run("Lists resources after the first", listResources(conjur, &ResourceFilter{Offset: 1}, 10)) } func TestClient_Resource(t *testing.T) { @@ -148,18 +111,8 @@ func TestClient_Resource(t *testing.T) { } } - t.Run("V5", func(t *testing.T) { - conjur, err := v5Setup() - assert.NoError(t, err) - - t.Run("Shows a resource", showResource(conjur, "cucumber:variable:db-password")) - }) + conjur, err := conjurSetup() + assert.NoError(t, err) - if os.Getenv("TEST_VERSION") != "oss" { - t.Run("V4", func(t *testing.T) { - _, err := v4Setup() - assert.NoError(t, err) - // v4 router doesn't support it showResource - }) - } + t.Run("Shows a resource", showResource(conjur, "cucumber:variable:db-password")) } diff --git a/conjurapi/router_v4.go b/conjurapi/router_v4.go deleted file mode 100644 index 625b2a1..0000000 --- a/conjurapi/router_v4.go +++ /dev/null @@ -1,127 +0,0 @@ -package conjurapi - -import ( - "fmt" - "io" - "net/http" - "net/url" - "strings" - - "github.com/cyberark/conjur-api-go/conjurapi/authn" - "github.com/sirupsen/logrus" -) - -type RouterV4 struct { - Config *Config -} - -func (r RouterV4) AuthenticateRequest(loginPair authn.LoginPair) (*http.Request, error) { - authenticateURL := fmt.Sprintf("%s/authn/users/%s/authenticate", r.Config.ApplianceURL, url.QueryEscape(loginPair.Login)) - - req, err := http.NewRequest("POST", authenticateURL, strings.NewReader(loginPair.APIKey)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "text/plain") - - return req, nil -} - -func (r RouterV4) RotateAPIKeyRequest(roleID string) (*http.Request, error) { - account, kind, id, err := parseID(roleID) - if err != nil { - return nil, err - } - if account != r.Config.Account { - return nil, fmt.Errorf("Account of '%s' must match the configured account '%s'", roleID, r.Config.Account) - } - - var username string - switch kind { - case "user": - username = id - default: - username = strings.Join([]string{kind, id}, "/") - } - - rotateURL := fmt.Sprintf("%s/authn/users/api_key?id=%s", r.Config.ApplianceURL, url.QueryEscape(username)) - - return http.NewRequest( - "PUT", - rotateURL, - nil, - ) -} - -func (r RouterV4) LoadPolicyRequest(mode PolicyMode, policyID string, policy io.Reader) (*http.Request, error) { - return nil, fmt.Errorf("LoadPolicy is not supported for Conjur V4") -} - -func (r RouterV4) ResourceRequest(resourceID string) (*http.Request, error) { - logrus.Panic("ResourceRequest not implemented yet") - return nil, nil -} - -func (r RouterV4) ResourcesRequest(filter *ResourceFilter) (*http.Request, error) { - logrus.Panic("ResourcesRequest not implemented yet") - return nil, nil -} - -func (r RouterV4) CheckPermissionRequest(resourceID, privilege string) (*http.Request, error) { - account, kind, id, err := parseID(resourceID) - if err != nil { - return nil, err - } - - checkURL := fmt.Sprintf("%s/authz/%s/resources/%s/%s?check=true&privilege=%s", r.Config.ApplianceURL, account, kind, url.QueryEscape(id), url.QueryEscape(privilege)) - - return http.NewRequest( - "GET", - checkURL, - nil, - ) -} - -func (r RouterV4) AddSecretRequest(variableID, secretValue string) (*http.Request, error) { - return nil, fmt.Errorf("AddSecret is not supported for Conjur V4") -} - -func (r RouterV4) RetrieveBatchSecretsRequest(variableIDs []string, base64Flag bool) (*http.Request, error) { - if base64Flag { - return nil, fmt.Errorf("Batch retrieving Base64-encoded secrets is not supported in Conjur V4") - } - - return http.NewRequest( - "GET", - r.batchVariableURL(variableIDs), - nil, - ) -} - -func (r RouterV4) RetrieveSecretRequest(variableID string) (*http.Request, error) { - fullVariableID := makeFullId(r.Config.Account, "variable", variableID) - - variableURL, err := r.variableURL(fullVariableID) - if err != nil { - return nil, err - } - - return http.NewRequest( - "GET", - variableURL, - nil, - ) -} - -func (r RouterV4) variableURL(variableID string) (string, error) { - _, _, id, err := parseID(variableID) - if err != nil { - return "", err - } - return fmt.Sprintf("%s/variables/%s/value", r.Config.ApplianceURL, url.PathEscape(id)), nil -} - -func (r RouterV4) batchVariableURL(variableIDs []string) string { - queryString := url.QueryEscape(strings.Join(variableIDs, ",")) - return fmt.Sprintf("%s/variables/values?vars=%s", r.Config.ApplianceURL, queryString) -} diff --git a/conjurapi/router_v5.go b/conjurapi/router_v5.go deleted file mode 100644 index 2e7ac89..0000000 --- a/conjurapi/router_v5.go +++ /dev/null @@ -1,225 +0,0 @@ -package conjurapi - -import ( - "fmt" - "io" - "net/http" - "net/url" - "strconv" - "strings" - - "github.com/cyberark/conjur-api-go/conjurapi/authn" -) - -type RouterV5 struct { - Config *Config -} - -func (r RouterV5) AuthenticateRequest(loginPair authn.LoginPair) (*http.Request, error) { - authenticateURL := makeRouterURL(r.authnURL(), url.QueryEscape(loginPair.Login), "authenticate").String() - - req, err := http.NewRequest("POST", authenticateURL, strings.NewReader(loginPair.APIKey)) - if err != nil { - return nil, err - } - req.Header.Set("Content-Type", "text/plain") - - return req, nil -} - -func (r RouterV5) RotateAPIKeyRequest(roleID string) (*http.Request, error) { - account, _, _, err := parseID(roleID) - if err != nil { - return nil, err - } - if account != r.Config.Account { - return nil, fmt.Errorf("Account of '%s' must match the configured account '%s'", roleID, r.Config.Account) - } - - rotateURL := makeRouterURL(r.authnURL(), "api_key").withFormattedQuery("role=%s", roleID).String() - - return http.NewRequest( - "PUT", - rotateURL, - nil, - ) -} - -func (r RouterV5) CheckPermissionRequest(resourceID, privilege string) (*http.Request, error) { - account, kind, id, err := parseID(resourceID) - if err != nil { - return nil, err - } - checkURL := makeRouterURL(r.resourcesURL(account), kind, url.QueryEscape(id)).withFormattedQuery("check=true&privilege=%s", url.QueryEscape(privilege)).String() - - return http.NewRequest( - "GET", - checkURL, - nil, - ) -} - -func (r RouterV5) ResourceRequest(resourceID string) (*http.Request, error) { - account, kind, id, err := parseID(resourceID) - if err != nil { - return nil, err - } - - requestURL := makeRouterURL(r.resourcesURL(account), kind, url.QueryEscape(id)) - - return http.NewRequest( - "GET", - requestURL.String(), - nil, - ) -} - -func (r RouterV5) ResourcesRequest(filter *ResourceFilter) (*http.Request, error) { - query := url.Values{} - - if filter != nil { - if filter.Kind != "" { - query.Add("kind", filter.Kind) - } - if filter.Search != "" { - query.Add("search", filter.Search) - } - - if filter.Limit != 0 { - query.Add("limit", strconv.Itoa(filter.Limit)) - } - - if filter.Offset != 0 { - query.Add("offset", strconv.Itoa(filter.Offset)) - } - } - - requestURL := makeRouterURL(r.resourcesURL(r.Config.Account)).withQuery(query.Encode()) - - return http.NewRequest( - "GET", - requestURL.String(), - nil, - ) -} - -func (r RouterV5) LoadPolicyRequest(mode PolicyMode, policyID string, policy io.Reader) (*http.Request, error) { - fullPolicyID := makeFullId(r.Config.Account, "policy", policyID) - - account, kind, id, err := parseID(fullPolicyID) - if err != nil { - return nil, err - } - policyURL := makeRouterURL(r.policiesURL(account), kind, url.QueryEscape(id)).String() - - var method string - switch mode { - case PolicyModePost: - method = "POST" - case PolicyModePatch: - method = "PATCH" - case PolicyModePut: - method = "PUT" - default: - return nil, fmt.Errorf("Invalid PolicyMode : %d", mode) - } - - return http.NewRequest( - method, - policyURL, - policy, - ) -} - -func (r RouterV5) RetrieveBatchSecretsRequest(variableIDs []string, base64Flag bool) (*http.Request, error) { - fullVariableIDs := []string{} - for _, variableID := range variableIDs { - fullVariableID := makeFullId(r.Config.Account, "variable", variableID) - fullVariableIDs = append(fullVariableIDs, fullVariableID) - } - - request, err := http.NewRequest( - "GET", - r.batchVariableURL(fullVariableIDs), - nil, - ) - - if err != nil { - return nil, err - } - - if base64Flag { - request.Header.Add("Accept-Encoding", "base64") - } - - return request, nil -} - -func (r RouterV5) RetrieveSecretRequest(variableID string) (*http.Request, error) { - fullVariableID := makeFullId(r.Config.Account, "variable", variableID) - - variableURL, err := r.variableURL(fullVariableID) - if err != nil { - return nil, err - } - - return http.NewRequest( - "GET", - variableURL, - nil, - ) -} - -func (r RouterV5) AddSecretRequest(variableID, secretValue string) (*http.Request, error) { - fullVariableID := makeFullId(r.Config.Account, "variable", variableID) - - variableURL, err := r.variableURL(fullVariableID) - if err != nil { - return nil, err - } - - request, err := http.NewRequest( - "POST", - variableURL, - strings.NewReader(secretValue), - ) - if err != nil { - return nil, err - } - - request.Header.Add("Content-Type", "application/x-www-form-urlencoded") - return request, nil -} - -func (r RouterV5) variableURL(variableID string) (string, error) { - account, kind, id, err := parseID(variableID) - if err != nil { - return "", err - } - return makeRouterURL(r.secretsURL(account), kind, url.PathEscape(id)).String(), nil -} - -func (r RouterV5) batchVariableURL(variableIDs []string) string { - queryString := url.QueryEscape(strings.Join(variableIDs, ",")) - return makeRouterURL(r.globalSecretsURL()).withFormattedQuery("variable_ids=%s", queryString).String() -} - -func (r RouterV5) authnURL() string { - return makeRouterURL(r.Config.ApplianceURL, "authn", r.Config.Account).String() -} - -func (r RouterV5) resourcesURL(account string) string { - return makeRouterURL(r.Config.ApplianceURL, "resources", account).String() -} - -func (r RouterV5) secretsURL(account string) string { - return makeRouterURL(r.Config.ApplianceURL, "secrets", account).String() -} - -func (r RouterV5) globalSecretsURL() string { - return makeRouterURL(r.Config.ApplianceURL, "secrets").String() -} - -func (r RouterV5) policiesURL(account string) string { - return makeRouterURL(r.Config.ApplianceURL, "policies", account).String() -} diff --git a/conjurapi/variable.go b/conjurapi/variable.go index d7eb13c..531c87a 100644 --- a/conjurapi/variable.go +++ b/conjurapi/variable.go @@ -80,7 +80,7 @@ func (c *Client) RetrieveSecretReader(variableID string) (io.ReadCloser, error) } func (c *Client) retrieveBatchSecrets(variableIDs []string, base64Flag bool) (map[string]string, error) { - req, err := c.router.RetrieveBatchSecretsRequest(variableIDs, base64Flag) + req, err := c.RetrieveBatchSecretsRequest(variableIDs, base64Flag) if err != nil { return nil, err } @@ -112,7 +112,7 @@ func (c *Client) retrieveBatchSecrets(variableIDs []string, base64Flag bool) (ma } func (c *Client) retrieveSecret(variableID string) (*http.Response, error) { - req, err := c.router.RetrieveSecretRequest(variableID) + req, err := c.RetrieveSecretRequest(variableID) if err != nil { return nil, err } @@ -124,7 +124,7 @@ func (c *Client) retrieveSecret(variableID string) (*http.Response, error) { // // The authenticated user must have update privilege on the variable. func (c *Client) AddSecret(variableID string, secretValue string) error { - req, err := c.router.AddSecretRequest(variableID, secretValue) + req, err := c.AddSecretRequest(variableID, secretValue) if err != nil { return err } diff --git a/conjurapi/variable_test.go b/conjurapi/variable_test.go index 49c6f74..11ab6b9 100644 --- a/conjurapi/variable_test.go +++ b/conjurapi/variable_test.go @@ -13,383 +13,246 @@ import ( ) func TestClient_RetrieveSecret(t *testing.T) { - t.Run("V5", func(t *testing.T) { - config := &Config{} - config.mergeEnv() + config := &Config{} + config.mergeEnv() - login := os.Getenv("CONJUR_AUTHN_LOGIN") - apiKey := os.Getenv("CONJUR_AUTHN_API_KEY") + login := os.Getenv("CONJUR_AUTHN_LOGIN") + apiKey := os.Getenv("CONJUR_AUTHN_API_KEY") - t.Run("On a populated secret", func(t *testing.T) { - variableIdentifier := "existent-variable-with-defined-value" - secretValue := fmt.Sprintf("secret-value-%v", rand.Intn(123456)) - policy := fmt.Sprintf(` + t.Run("On a populated secret", func(t *testing.T) { + variableIdentifier := "existent-variable-with-defined-value" + secretValue := fmt.Sprintf("secret-value-%v", rand.Intn(123456)) + policy := fmt.Sprintf(` - !variable %s `, variableIdentifier) - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) - - conjur.LoadPolicy( - PolicyModePut, - "root", - strings.NewReader(policy), - ) - err = conjur.AddSecret(variableIdentifier, secretValue) - assert.NoError(t, err) - - t.Run("Returns existent variable's defined value as a stream", func(t *testing.T) { - secretResponse, err := conjur.RetrieveSecretReader(variableIdentifier) - assert.NoError(t, err) - - obtainedSecretValue, err := ReadResponseBody(secretResponse) - assert.NoError(t, err) - - assert.Equal(t, secretValue, string(obtainedSecretValue)) - }) - - t.Run("Returns existent variable's defined value", func(t *testing.T) { - obtainedSecretValue, err := conjur.RetrieveSecret(variableIdentifier) - assert.NoError(t, err) - - assert.Equal(t, secretValue, string(obtainedSecretValue)) - }) - - t.Run("Handles a fully qualified variable id", func(t *testing.T) { - obtainedSecretValue, err := conjur.RetrieveSecret("cucumber:variable:" + variableIdentifier) - assert.NoError(t, err) - - assert.Equal(t, secretValue, string(obtainedSecretValue)) - }) - - t.Run("Prepends the account name automatically", func(t *testing.T) { - obtainedSecretValue, err := conjur.RetrieveSecret("variable:" + variableIdentifier) - assert.NoError(t, err) - - assert.Equal(t, secretValue, string(obtainedSecretValue)) - }) - - t.Run("Rejects an id from the wrong account", func(t *testing.T) { - _, err := conjur.RetrieveSecret("foobar:variable:" + variableIdentifier) - - conjurError := err.(*response.ConjurError) - assert.Equal(t, 404, conjurError.Code) - }) - - t.Run("Rejects an id with the wrong kind", func(t *testing.T) { - _, err := conjur.RetrieveSecret("cucumber:waffle:" + variableIdentifier) - - conjurError := err.(*response.ConjurError) - assert.Equal(t, 404, conjurError.Code) - }) - }) - - t.Run("On many populated secrets", func(t *testing.T) { - variables := map[string]string{ - "myapp-01": "these", - "alice@devops": "are", - "prod/aws/db-password": "all", - "research+development": "secret", - "sales&marketing": "strings!", - "onemore": "{\"json\": \"object\"}", - "a/ b /c": "somevalue", - } - binaryVariables := map[string]string{ - "binary1": "test\xf0\xf1", - "binary2": "tes\xf0t\xf1i\xf2ng", - "nonBinary": "testing", - } - - policy := "" - for id := range variables { - policy = fmt.Sprintf("%s- !variable %s\n", policy, id) - } + conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + assert.NoError(t, err) - for id := range binaryVariables { - policy = fmt.Sprintf("%s- !variable %s\n", policy, id) - } + conjur.LoadPolicy( + PolicyModePut, + "root", + strings.NewReader(policy), + ) + err = conjur.AddSecret(variableIdentifier, secretValue) + assert.NoError(t, err) - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + t.Run("Returns existent variable's defined value as a stream", func(t *testing.T) { + secretResponse, err := conjur.RetrieveSecretReader(variableIdentifier) assert.NoError(t, err) - conjur.LoadPolicy( - PolicyModePut, - "root", - strings.NewReader(policy), - ) - - for id, value := range variables { - err = conjur.AddSecret(id, value) - assert.NoError(t, err) - } - - for id, value := range binaryVariables { - err = conjur.AddSecret(id, value) - assert.NoError(t, err) - } + obtainedSecretValue, err := ReadResponseBody(secretResponse) + assert.NoError(t, err) - t.Run("Fetch many secrets in a single batch retrieval", func(t *testing.T) { - variableIds := []string{} - for id := range variables { - variableIds = append(variableIds, id) - } - - secrets, err := conjur.RetrieveBatchSecrets(variableIds) - assert.NoError(t, err) - - for id, value := range variables { - fullyQualifiedID := fmt.Sprintf("%s:variable:%s", config.Account, id) - fetchedValue, ok := secrets[fullyQualifiedID] - assert.True(t, ok) - assert.Equal(t, value, string(fetchedValue)) - } - }) - - t.Run("Fetch binary secrets in a batch request", func(t *testing.T) { - variableIds := []string{} - for id := range binaryVariables { - variableIds = append(variableIds, id) - } - - secrets, err := conjur.RetrieveBatchSecretsSafe(variableIds) - assert.NoError(t, err) - - for id, value := range binaryVariables { - fullyQualifiedID := fmt.Sprintf("%s:variable:%s", config.Account, id) - fetchedValue, ok := secrets[fullyQualifiedID] - assert.True(t, ok) - assert.Equal(t, value, string(fetchedValue)) - } - }) - - t.Run("Fail to fetch binary secrets in batch request", func(t *testing.T) { - variableIds := []string{} - for id := range binaryVariables { - variableIds = append(variableIds, id) - } - - _, err := conjur.RetrieveBatchSecrets(variableIds) - assert.Error(t, err) - assert.Contains(t, err.Error(), "Issue encoding secret into JSON format") - conjurError := err.(*response.ConjurError) - assert.Equal(t, 406, conjurError.Code) - }) + assert.Equal(t, secretValue, string(obtainedSecretValue)) }) - t.Run("Token authenticator can be used to fetch a secret", func(t *testing.T) { - variableIdentifier := "existent-variable-with-defined-value" - secretValue := fmt.Sprintf("secret-value-%v", rand.Intn(123456)) - policy := fmt.Sprintf(` - - !variable %s - `, variableIdentifier) - - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + t.Run("Returns existent variable's defined value", func(t *testing.T) { + obtainedSecretValue, err := conjur.RetrieveSecret(variableIdentifier) assert.NoError(t, err) - conjur.LoadPolicy( - PolicyModePut, - "root", - strings.NewReader(policy), - ) - conjur.AddSecret(variableIdentifier, secretValue) - - token, err := conjur.authenticator.RefreshToken() - assert.NoError(t, err) + assert.Equal(t, secretValue, string(obtainedSecretValue)) + }) - conjur, err = NewClientFromToken(*config, string(token)) + t.Run("Handles a fully qualified variable id", func(t *testing.T) { + obtainedSecretValue, err := conjur.RetrieveSecret("cucumber:variable:" + variableIdentifier) assert.NoError(t, err) - obtainedSecretValue, err := conjur.RetrieveSecret(variableIdentifier) - assert.NoError(t, err) assert.Equal(t, secretValue, string(obtainedSecretValue)) }) - t.Run("Returns 404 on existent variable with undefined value", func(t *testing.T) { - variableIdentifier := "existent-variable-with-undefined-value" - policy := fmt.Sprintf(` - - !variable %s - `, variableIdentifier) - - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + t.Run("Prepends the account name automatically", func(t *testing.T) { + obtainedSecretValue, err := conjur.RetrieveSecret("variable:" + variableIdentifier) assert.NoError(t, err) - conjur.LoadPolicy( - PolicyModePut, - "root", - strings.NewReader(policy), - ) + assert.Equal(t, secretValue, string(obtainedSecretValue)) + }) - _, err = conjur.RetrieveSecret(variableIdentifier) + t.Run("Rejects an id from the wrong account", func(t *testing.T) { + _, err := conjur.RetrieveSecret("foobar:variable:" + variableIdentifier) - assert.Error(t, err) - assert.Contains(t, err.Error(), "CONJ00076E Variable cucumber:variable:existent-variable-with-undefined-value is empty or not found") conjurError := err.(*response.ConjurError) assert.Equal(t, 404, conjurError.Code) - assert.Equal(t, "not_found", conjurError.Details.Code) }) - t.Run("Returns 404 on non-existent variable", func(t *testing.T) { - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) - - _, err = conjur.RetrieveSecret("non-existent-variable") + t.Run("Rejects an id with the wrong kind", func(t *testing.T) { + _, err := conjur.RetrieveSecret("cucumber:waffle:" + variableIdentifier) - assert.Error(t, err) - assert.Contains(t, err.Error(), "CONJ00076E Variable cucumber:variable:non-existent-variable is empty or not found") conjurError := err.(*response.ConjurError) assert.Equal(t, 404, conjurError.Code) - assert.Equal(t, "not_found", conjurError.Details.Code) - }) - - t.Run("Given configuration has invalid login credentials", func(t *testing.T) { - login = "invalid-user" - - t.Run("Returns 401", func(t *testing.T) { - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) - - _, err = conjur.RetrieveSecret("existent-or-non-existent-variable") - - assert.Error(t, err) - assert.Contains(t, err.Error(), "Unauthorized") - conjurError := err.(*response.ConjurError) - assert.Equal(t, 401, conjurError.Code) - }) }) }) - if os.Getenv("TEST_VERSION") != "oss" { - t.Run("V4", func(t *testing.T) { - config := &Config{ - ApplianceURL: os.Getenv("CONJUR_V4_APPLIANCE_URL"), - SSLCert: os.Getenv("CONJUR_V4_SSL_CERTIFICATE"), - Account: os.Getenv("CONJUR_V4_ACCOUNT"), - V4: true, - } + t.Run("On many populated secrets", func(t *testing.T) { + variables := map[string]string{ + "myapp-01": "these", + "alice@devops": "are", + "prod/aws/db-password": "all", + "research+development": "secret", + "sales&marketing": "strings!", + "onemore": "{\"json\": \"object\"}", + "a/ b /c": "somevalue", + } + binaryVariables := map[string]string{ + "binary1": "test\xf0\xf1", + "binary2": "tes\xf0t\xf1i\xf2ng", + "nonBinary": "testing", + } + + policy := "" + for id := range variables { + policy = fmt.Sprintf("%s- !variable %s\n", policy, id) + } + + for id := range binaryVariables { + policy = fmt.Sprintf("%s- !variable %s\n", policy, id) + } + + conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + assert.NoError(t, err) + + conjur.LoadPolicy( + PolicyModePut, + "root", + strings.NewReader(policy), + ) + + for id, value := range variables { + err = conjur.AddSecret(id, value) + assert.NoError(t, err) + } - login := os.Getenv("CONJUR_V4_AUTHN_LOGIN") - apiKey := os.Getenv("CONJUR_V4_AUTHN_API_KEY") + for id, value := range binaryVariables { + err = conjur.AddSecret(id, value) + assert.NoError(t, err) + } - t.Run("Returns existent variable's defined value, given full qualified ID", func(t *testing.T) { - variableIdentifier := "cucumber:variable:existent-variable-with-defined-value" - secretValue := "existent-variable-defined-value" + t.Run("Fetch many secrets in a single batch retrieval", func(t *testing.T) { + variableIds := []string{} + for id := range variables { + variableIds = append(variableIds, id) + } - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + secrets, err := conjur.RetrieveBatchSecrets(variableIds) + assert.NoError(t, err) - obtainedSecretValue, err := conjur.RetrieveSecret(variableIdentifier) - assert.NoError(t, err) + for id, value := range variables { + fullyQualifiedID := fmt.Sprintf("%s:variable:%s", config.Account, id) + fetchedValue, ok := secrets[fullyQualifiedID] + assert.True(t, ok) + assert.Equal(t, value, string(fetchedValue)) + } + }) - assert.Equal(t, secretValue, string(obtainedSecretValue)) - }) + t.Run("Fetch binary secrets in a batch request", func(t *testing.T) { + variableIds := []string{} + for id := range binaryVariables { + variableIds = append(variableIds, id) + } - t.Run("Returns existent variable's defined value", func(t *testing.T) { - variableIdentifier := "existent-variable-with-defined-value" - secretValue := "existent-variable-defined-value" + secrets, err := conjur.RetrieveBatchSecretsSafe(variableIds) + assert.NoError(t, err) - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + for id, value := range binaryVariables { + fullyQualifiedID := fmt.Sprintf("%s:variable:%s", config.Account, id) + fetchedValue, ok := secrets[fullyQualifiedID] + assert.True(t, ok) + assert.Equal(t, value, string(fetchedValue)) + } + }) - obtainedSecretValue, err := conjur.RetrieveSecret(variableIdentifier) - assert.NoError(t, err) + t.Run("Fail to fetch binary secrets in batch request", func(t *testing.T) { + variableIds := []string{} + for id := range binaryVariables { + variableIds = append(variableIds, id) + } - assert.Equal(t, secretValue, string(obtainedSecretValue)) - }) + _, err := conjur.RetrieveBatchSecrets(variableIds) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Issue encoding secret into JSON format") + conjurError := err.(*response.ConjurError) + assert.Equal(t, 406, conjurError.Code) + }) + }) - t.Run("Returns space-pathed existent variable's defined value", func(t *testing.T) { - variableIdentifier := "a/ b/c" - secretValue := "a/ b/c" + t.Run("Token authenticator can be used to fetch a secret", func(t *testing.T) { + variableIdentifier := "existent-variable-with-defined-value" + secretValue := fmt.Sprintf("secret-value-%v", rand.Intn(123456)) + policy := fmt.Sprintf(` + - !variable %s + `, variableIdentifier) - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + assert.NoError(t, err) - obtainedSecretValue, err := conjur.RetrieveSecret(variableIdentifier) - assert.NoError(t, err) + conjur.LoadPolicy( + PolicyModePut, + "root", + strings.NewReader(policy), + ) + conjur.AddSecret(variableIdentifier, secretValue) - assert.Equal(t, secretValue, string(obtainedSecretValue)) - }) + token, err := conjur.authenticator.RefreshToken() + assert.NoError(t, err) - t.Run("Returns 404 on existent variable with undefined value", func(t *testing.T) { - variableIdentifier := "existent-variable-with-undefined-value" + conjur, err = NewClientFromToken(*config, string(token)) + assert.NoError(t, err) - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + obtainedSecretValue, err := conjur.RetrieveSecret(variableIdentifier) + assert.NoError(t, err) + assert.Equal(t, secretValue, string(obtainedSecretValue)) + }) - _, err = conjur.RetrieveSecret(variableIdentifier) - assert.Error(t, err) - assert.Contains(t, err.Error(), "Not Found") - conjurError := err.(*response.ConjurError) - assert.Equal(t, 404, conjurError.Code) - }) + t.Run("Returns 404 on existent variable with undefined value", func(t *testing.T) { + variableIdentifier := "existent-variable-with-undefined-value" + policy := fmt.Sprintf(` + - !variable %s + `, variableIdentifier) - t.Run("Returns 404 on non-existent variable", func(t *testing.T) { - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + assert.NoError(t, err) - _, err = conjur.RetrieveSecret("non-existent-variable") + conjur.LoadPolicy( + PolicyModePut, + "root", + strings.NewReader(policy), + ) - assert.Error(t, err) - assert.Contains(t, err.Error(), "variable 'non-existent-variable' not found") - conjurError := err.(*response.ConjurError) - assert.Equal(t, 404, conjurError.Code) - }) + _, err = conjur.RetrieveSecret(variableIdentifier) - t.Run("Fetch many secrets in a single batch retrieval", func(t *testing.T) { - variables := map[string]string{ - "myapp-01": "these", - "alice@devops": "are", - "prod/aws/db-password": "all", - "research+development": "secret", - "sales&marketing": "strings", - "onemore": "{\"json\": \"object\"}", - } + assert.Error(t, err) + assert.Contains(t, err.Error(), "CONJ00076E Variable cucumber:variable:existent-variable-with-undefined-value is empty or not found") + conjurError := err.(*response.ConjurError) + assert.Equal(t, 404, conjurError.Code) + assert.Equal(t, "not_found", conjurError.Details.Code) + }) - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) + t.Run("Returns 404 on non-existent variable", func(t *testing.T) { + conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + assert.NoError(t, err) - variableIds := []string{} - for id := range variables { - variableIds = append(variableIds, id) - } + _, err = conjur.RetrieveSecret("non-existent-variable") - secrets, err := conjur.RetrieveBatchSecrets(variableIds) - assert.NoError(t, err) + assert.Error(t, err) + assert.Contains(t, err.Error(), "CONJ00076E Variable cucumber:variable:non-existent-variable is empty or not found") + conjurError := err.(*response.ConjurError) + assert.Equal(t, 404, conjurError.Code) + assert.Equal(t, "not_found", conjurError.Details.Code) + }) - for id, value := range variables { - fetchedValue, ok := secrets[id] - assert.True(t, ok) - assert.Equal(t, value, string(fetchedValue)) - } + t.Run("Given configuration has invalid login credentials", func(t *testing.T) { + login = "invalid-user" - t.Run("Fail to use the safe method for batch retrieval", func(t *testing.T) { - _, err := conjur.RetrieveBatchSecretsSafe(variableIds) - assert.Contains(t, err.Error(), "not supported in Conjur V4") - }) + t.Run("Returns 401", func(t *testing.T) { + conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) + assert.NoError(t, err) - t.Run("Fail to retrieve binary secret in batch retrieval", func(t *testing.T) { - variableIds = append(variableIds, "binary") + _, err = conjur.RetrieveSecret("existent-or-non-existent-variable") - _, err := conjur.RetrieveBatchSecrets(variableIds) - conjurError := err.(*response.ConjurError) - assert.Equal(t, 500, conjurError.Code) - }) - }) - - t.Run("Given configuration has invalid login credentials", func(t *testing.T) { - login = "invalid-user" - - t.Run("Returns 401", func(t *testing.T) { - conjur, err := NewClientFromKey(*config, authn.LoginPair{Login: login, APIKey: apiKey}) - assert.NoError(t, err) - - _, err = conjur.RetrieveSecret("existent-or-non-existent-variable") - - assert.Error(t, err) - assert.Contains(t, err.Error(), "Unauthorized") - conjurError := err.(*response.ConjurError) - assert.Equal(t, 401, conjurError.Code) - }) - }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "Unauthorized") + conjurError := err.(*response.ConjurError) + assert.Equal(t, 401, conjurError.Code) }) - } + }) } diff --git a/docker-compose.yml b/docker-compose.yml index 4dd706b..ccde45b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -19,13 +19,6 @@ services: CONJUR_APPLIANCE_URL: http://conjur CONJUR_ACCOUNT: cucumber - cuke-master: - image: registry2.itci.conjur.net/conjur-appliance-cuke-master:4.9-stable - security_opt: - - seccomp:unconfined - ports: - - 443 - test-1.17: build: context: . @@ -43,13 +36,6 @@ services: CONJUR_ACCOUNT: cucumber CONJUR_AUTHN_LOGIN: admin CONJUR_AUTHN_API_KEY: - CONJUR_V4_APPLIANCE_URL: https://cuke-master/api - CONJUR_V4_HEALTH_URL: https://cuke-master/health - CONJUR_V4_ACCOUNT: cucumber - CONJUR_V4_AUTHN_LOGIN: admin - CONJUR_V4_AUTHN_API_KEY: - CONJUR_V4_SSL_CERTIFICATE: - TEST_VERSION: GO_VERSION: test-1.18: @@ -69,13 +55,6 @@ services: CONJUR_ACCOUNT: cucumber CONJUR_AUTHN_LOGIN: admin CONJUR_AUTHN_API_KEY: - CONJUR_V4_APPLIANCE_URL: https://cuke-master/api - CONJUR_V4_HEALTH_URL: https://cuke-master/health - CONJUR_V4_ACCOUNT: cucumber - CONJUR_V4_AUTHN_LOGIN: admin - CONJUR_V4_AUTHN_API_KEY: - CONJUR_V4_SSL_CERTIFICATE: - TEST_VERSION: GO_VERSION: dev: @@ -95,11 +74,5 @@ services: CONJUR_ACCOUNT: cucumber CONJUR_AUTHN_LOGIN: admin CONJUR_AUTHN_API_KEY: - CONJUR_V4_APPLIANCE_URL: https://cuke-master/api - CONJUR_V4_HEALTH_URL: https://cuke-master/health - CONJUR_V4_ACCOUNT: cucumber - CONJUR_V4_AUTHN_LOGIN: admin - CONJUR_V4_AUTHN_API_KEY: - CONJUR_V4_SSL_CERTIFICATE: entrypoint: sleep command: infinity