Skip to content

Commit

Permalink
Migrate CI to SaaS (#216) (#220)
Browse files Browse the repository at this point in the history
* Update ci.yml for Pineapple

* Fix indentation error

* Revert to previous checkout/install

* Add /api to CC URL

* move to ubuntu-latest hosted runner

* switch from ldap to identity

* Change test account to cyberark

* Change PAS_ADDRESS to PAS_HOSTNAME

* removed sleep

* Update IDs for SaaS

* accountSafeName _ to -

* Create new test account

* Add Content-Length to request header

* Add empty struct for body on POST

* Add emptyBody to logoff

* Change ListSafeMembers.Members.value.MemberId to interface{}

* Update RemoveSafeMember from v1 to v2 API

* Add MemberType to Add Safe Member test

* Removed early DeleteSafe

* Add emptyBody to UnsuspendUser

* Add Create Temp PEM Files step

* Update CCP variable paths

* Base64 decode CCP Certs

* Add CCP_HOSTNAME env var

* Move test workflow to self-hosted runner

* Update CCP_CLIENT_PRIVATE_KEY
  • Loading branch information
infamousjoeg committed Dec 13, 2023
1 parent 9de3924 commit 2d6d5ff
Show file tree
Hide file tree
Showing 12 changed files with 75 additions and 114 deletions.
81 changes: 12 additions & 69 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '>=1.16'
go-version: '>=1.18'
cache: false
- name: Lint All
uses: golangci/golangci-lint-action@v3
Expand All @@ -28,87 +28,30 @@ jobs:
test:
name: Test
runs-on: self-hosted
needs:
- lint
needs: lint
permissions:
id-token: write
contents: read
# env:
# PAS_HOSTNAME: ${{ secrets.PAS_HOSTNAME }}
# CCP_CLIENT_CERT: ${{ secrets.CCP_CLIENT_CERT }}
# CCP_CLIENT_PRIVATE_KEY: ${{ secrets.CCP_CLIENT_PRIVATE_KEY }}
steps:
- name: Checkout Source Code
- name: Checkout source code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '>=1.16'
go-version: '>=1.18'
cache: false
- name: Import Secrets using CyberArk Conjur Secret Fetcher
uses: infamousjoeg/conjur-action@v2.0.4
with:
url: https://infamous.secretsmgr.cyberark.cloud
url: https://pineapple.secretsmgr.cyberark.cloud/api
account: conjur
authn_id: github
authn_id: inf-github
secrets: |
data/vault/D-App-CybrCLI/Application-CyberArkIdentitySecurity-infamous.cyberark.cloud-cybr-cli@cyberark.cloud.13142/address|PAS_ADDRESS;data/vault/D-App-CybrCLI/Application-CyberArkIdentitySecurity-infamous.cyberark.cloud-cybr-cli@cyberark.cloud.13142/username|PAS_USERNAME;data/vault/D-App-CybrCLI/Application-CyberArkIdentitySecurity-infamous.cyberark.cloud-cybr-cli@cyberark.cloud.13142/password|PAS_PASSWORD;data/vault/D-App-CybrCLI/ccp-client-certificate/password|CCP_CLIENT_CERT;data/vault/D-App-CybrCLI/ccp-priv-key/password|CCP_CLIENT_PRIVATE_KEY
- name: Debug Step
run: |
echo "PAS_ADDRESS: " $PAS_ADDRESS "\r\nPAS_USERNAME: " $PAS_USERNAME "\r\nPAS_PASSWORD: " $PAS_PASSWORD "\r\nCCP_CLIENT_CERT: " $CCP_CLIENT_CERT "\r\nCCP_CLIENT_PRIVATE_KEY: " $CCP_CLIENT_PRIVATE_KEY > secrets.txt
- name: Upload Artifacts to Workflow
if: always()
uses: actions/upload-artifact@v2
with:
name: Secrets
path: |
secrets.txt
data/vault/PIN-APP-CYBRCLI/Application-CyberArk-httpspineapple.privilegecloud.cyberark.cloud-jgarcia/address|PAS_HOSTNAME;data/vault/PIN-APP-CYBRCLI/Application-CyberArk-httpspineapple.privilegecloud.cyberark.cloud-jgarcia/username|PAS_USERNAME;data/vault/PIN-APP-CYBRCLI/Application-CyberArk-httpspineapple.privilegecloud.cyberark.cloud-jgarcia/password|PAS_PASSWORD;data/vault/PIN-APP-CYBRCLI/Website-PIN-CLIENT-CERT-httpscloud-connect.infamousdevops.com-ccp_client_cert/password|CCP_CLIENT_CERT;data/vault/PIN-APP-CYBRCLI/Website-PIN-CLIENT-CERT-ccp.infamousdevops.com-ccp_client_key/password|CCP_CLIENT_PRIVATE_KEY;"
- name: Test All
run: go test -v ./...

build:
name: Build Binaries
runs-on: ubuntu-latest
needs:
- lint
- test
defaults:
run:
shell: bash
strategy:
matrix:
goos: [linux, darwin, windows]
goarch: [amd64]
steps:
- name: Checkout source code
uses: actions/checkout@v3
- name: Install Go
uses: actions/setup-go@v4
with:
go-version: '>=1.16'
cache: false
- name: Get current date & time
id: date
run: echo "::set-output name=date::$(date +'%Y%m%d_%H%M%S')"
- name: Export GO111MODULE environment variable
run: export GO111MODULE=on
- name: Create ./bin/ directory
run: mkdir -p bin
- name: Fix x/sys Issues
run: go get -u golang.org/x/sys
- name: Build Binaries
run: |
CGO_ENABLED=0 GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }} go build -o ./bin/${{ matrix.goos }}_cybr .
- name: Build Docker Container Package
run: |
docker build -t nfmsjoeg/cybr-cli:$TAG_NAME .
docker save nfmsjoeg/cybr-cli:$TAG_NAME > ./bin/docker_authenticator.tar
env:
TAG_NAME: alpha-${{ steps.date.outputs.date }}
- name: Upload Artifacts to Workflow
if: always()
uses: actions/upload-artifact@v2
with:
name: Release Executables
path: |
./bin/*_cybr*
CCP_HOSTNAME: "https://ccp.infamousdevops.com"
run: |
export CCP_CLIENT_CERT=$(echo $CCP_CLIENT_CERT | base64 -d)
export CCP_CLIENT_PRIVATE_KEY=$(echo $CCP_CLIENT_PRIVATE_KEY | base64 -d)
go test -v ./...
8 changes: 7 additions & 1 deletion cmd/safes.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ var (
User string
// Group is the group to search for as a safe member
Group string
// MemberType is the type of member being added to the safe
MemberType string
)

var safesCmd = &cobra.Command{
Expand Down Expand Up @@ -231,7 +233,8 @@ var addMembersCmd = &cobra.Command{
Example Usage:
$ cybr safes add-member -s SafeName -m MemberName --list-account --use-account --retrieve-account
$ cybr safes add-member -s SafeName -m MemberName --role ApplicationIdentity`,
$ cybr safes add-member -s SafeName -m MemberName --role ApplicationIdentity --member-type user
$ cybr safes add-member -s SafeName -m MemberName --role ApplicationIdentity --member-type group`,
Run: func(cmd *cobra.Command, args []string) {
// Get config file written to local file system
client, err := pasapi.GetConfigWithLogger(getLogger())
Expand Down Expand Up @@ -265,6 +268,7 @@ var addMembersCmd = &cobra.Command{
SearchIn: SearchIn,
MembershipExpirationDate: MembershipExpirationDate,
Permissions: RolePermissions,
MemberType: MemberType,
}

// Add a safe with the configuration options given via CLI subcommands
Expand Down Expand Up @@ -436,6 +440,8 @@ func init() {
addMembersCmd.Flags().StringVarP(&MemberName, "member-name", "m", "", "Name of member being added to the desired safe")
addMembersCmd.MarkFlagRequired("member-name")
addMembersCmd.Flags().StringVarP(&SearchIn, "search-in", "i", "Vault", "Search in Domain or Vault")
addMembersCmd.Flags().StringVarP(&MemberType, "member-type", "t", "user", "Type of member being added to the safe: user (default) or group")
addMembersCmd.MarkFlagRequired("member-type")
addMembersCmd.Flags().StringVarP(&MembershipExpirationDate, "member-expiration-date", "e", "", "When the membership will expire")
addMembersCmd.Flags().StringVarP(&Role, "role", "r", "", "The role of the safe member being added for automated permissioning")
addMembersCmd.Flags().BoolVar(&UseAccounts, "use-accounts", false, "Use accounts in safe")
Expand Down
15 changes: 9 additions & 6 deletions pkg/cybr/api/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (
httpJson "github.com/infamousjoeg/cybr-cli/pkg/cybr/helpers/httpjson"
)

// Create an empty JSON body payload
var emptyBody = struct{}{}

// ListAccounts CyberArk user has access to
func (c Client) ListAccounts(query *queries.ListAccounts) (*responses.ListAccount, error) {
url := fmt.Sprintf("%s/passwordvault/api/Accounts%s", c.BaseURL, httpJson.GetURLQuery(query))
Expand Down Expand Up @@ -72,7 +75,7 @@ func (c Client) DeleteAccount(accountID string) error {
// GetJITAccess from a specific account
func (c Client) GetJITAccess(accountID string) error {
url := fmt.Sprintf("%s/passwordvault/api/Accounts/%s/grantAdministrativeAccess", c.BaseURL, accountID)
response, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
response, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
return fmt.Errorf("Failed to get JIT access for account '%s'. %s. %s", accountID, string(returnedError), err)
Expand All @@ -84,7 +87,7 @@ func (c Client) GetJITAccess(accountID string) error {
// RevokeJITAccess from a specific account
func (c Client) RevokeJITAccess(accountID string) error {
url := fmt.Sprintf("%s/passwordvault/api/Accounts/%s/RevokeAdministrativeAccess", c.BaseURL, accountID)
response, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
response, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
return fmt.Errorf("Failed to revoke JIT access for account '%s'. %s. %s", accountID, string(returnedError), err)
Expand Down Expand Up @@ -122,7 +125,7 @@ func (c Client) GetAccountSSHKey(accountID string, request requests.GetAccountPa
// VerifyAccountCredentials marks an account for verification
func (c Client) VerifyAccountCredentials(accountID string) error {
url := fmt.Sprintf("%s/passwordvault/API/Accounts/%s/Verify", c.BaseURL, accountID)
response, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
response, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
return fmt.Errorf("Failed to verify account '%s'. %s. %s", accountID, string(returnedError), err)
Expand Down Expand Up @@ -154,7 +157,7 @@ func (c Client) ChangeAccountCredentials(accountID string, changeEntireGroup boo
// ReconileAccountCredentials marks an account for reconciliation
func (c Client) ReconileAccountCredentials(accountID string) error {
url := fmt.Sprintf("%s/passwordvault/API/Accounts/%s/Reconcile", c.BaseURL, accountID)
response, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
response, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
return fmt.Errorf("Failed to mark reconcile on account '%s'. %s. %s", accountID, string(returnedError), err)
Expand All @@ -166,7 +169,7 @@ func (c Client) ReconileAccountCredentials(accountID string) error {
// Unlock removes a lock from an account
func (c Client) Unlock(accountID string) error {
url := fmt.Sprintf("%s/passwordvault/API/Accounts/%s/Unlock", c.BaseURL, accountID)
response, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
response, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
return fmt.Errorf("Failed to unlock account '%s'. %s. %s", accountID, string(returnedError), err)
Expand All @@ -178,7 +181,7 @@ func (c Client) Unlock(accountID string) error {
// CheckIn checks in an account that is checked out by the user
func (c Client) CheckIn(accountID string) error {
url := fmt.Sprintf("%s/passwordvault/API/Accounts/%s/CheckIn", c.BaseURL, accountID)
response, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
response, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
return fmt.Errorf("Failed to check-in account '%s'. %s. %s", accountID, string(returnedError), err)
Expand Down
13 changes: 7 additions & 6 deletions pkg/cybr/api/accounts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import (
)

var (
accountSafeName = "CLI_ACCOUNTS_TEST"
accountID = "110_3"
accountSSHKeyID = "110_15"
invalidAccountID = "202_5"
accountSafeName = "PIN-APP-CYBRCLI-TEST"
accountID = "162_6"
accountUsername = "test"
accountSSHKeyID = "162_4"
invalidAccountID = "999_9"
)

func TestListAccountSuccess(t *testing.T) {
Expand Down Expand Up @@ -45,12 +46,12 @@ func TestListAccountSearchSuccess(t *testing.T) {
func TestGetAccountSuccess(t *testing.T) {
client, err := defaultPASAPIClient(t)

account, err := client.GetAccount("110_3")
account, err := client.GetAccount(accountID)
if err != nil {
t.Errorf("Failed to get account. %s", err)
}

if account.UserName != "test" {
if account.UserName != accountUsername {
t.Errorf("Retrieved invalid account. Account has username '%s' and should be 'test'", account.UserName)
}
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/cybr/api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func (c *Client) Logon(req requests.Logon) error {
func (c Client) Logoff() error {
// Set URL for request
url := fmt.Sprintf("%s/passwordvault/api/auth/logoff", c.BaseURL)
_, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
_, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
return fmt.Errorf("Unable to logoff PAS REST API Web Service. %s", err)
}
Expand Down
7 changes: 4 additions & 3 deletions pkg/cybr/api/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,13 @@ var (
func defaultPASAPIClient(t *testing.T) (pasapi.Client, error) {
client := pasapi.Client{
BaseURL: hostname,
AuthType: "ldap",
AuthType: "cyberark",
}

creds := requests.Logon{
Username: username,
Password: password,
Username: username,
Password: password,
ConcurrentSession: true,
}

err := client.Logon(creds)
Expand Down
1 change: 1 addition & 0 deletions pkg/cybr/api/requests/addsafemember.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ type AddSafeMember struct {
SearchIn string `json:"SearchIn"`
MembershipExpirationDate string `json:"MembershipExpirationDate,omitempty"`
Permissions map[string]string `json:"Permissions,omitempty"`
MemberType string `json:"MemberType"`
}
2 changes: 1 addition & 1 deletion pkg/cybr/api/responses/listsafemembers.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type ListSafeMembers struct {
type Members struct {
SafeName string `json:"safeName"`
SafeNumber int `json:"safeNumber"`
MemberID int `json:"memberId"`
MemberID interface{} `json:"memberId"`
MemberName string `json:"memberName"`
MemberType string `json:"memberType"`
IsExpiredMembershipEnable bool `json:"isExpiredMembershipEnable"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/cybr/api/safes.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func (c Client) AddSafeMember(safeName string, addMember requests.AddSafeMember)

// RemoveSafeMember Remove a member from a specific safe
func (c Client) RemoveSafeMember(safeName string, member string) error {
url := fmt.Sprintf("%s/passwordvault/WebServices/PIMServices.svc/Safes/%s/Members/%s", c.BaseURL, url.QueryEscape(safeName), url.QueryEscape(member))
url := fmt.Sprintf("%s/passwordvault/api/Safes/%s/Members/%s", c.BaseURL, url.QueryEscape(safeName), url.QueryEscape(member))
response, err := httpJson.Delete(false, url, c.SessionToken, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
Expand Down
45 changes: 26 additions & 19 deletions pkg/cybr/api/safes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func TestListSafeMembersInvalidSafeName(t *testing.T) {
}
}

func TestAddRemoveSafeSuccess(t *testing.T) {
func TestAddSafeSuccess(t *testing.T) {
client, err := defaultPASAPIClient(t)

newSafe := requests.AddSafe{
Expand All @@ -106,27 +106,12 @@ func TestAddRemoveSafeSuccess(t *testing.T) {
if err != nil {
t.Errorf("Failed to create safe '%s' even though it should have been created successfully. %s", newSafe.SafeName, err)
}

err = client.DeleteSafe(newSafe.SafeName)
if err != nil {
t.Errorf("Failed to delete safe '%s' even though it should exist and should be deletable. %s", newSafe.SafeName, err)
}
}

func TestRemoveSafeFail(t *testing.T) {
client, err := defaultPASAPIClient(t)

safeName := "notRealSafeName"
err = client.DeleteSafe(safeName)
if err == nil {
t.Errorf("Client returned successful safe deletion even though safe '%s' should not exist", safeName)
}
}

func TestAddRemoveSafeMemberSuccess(t *testing.T) {
client, err := defaultPASAPIClient(t)

safeName := "PasswordManager"
safeName := "TestCreateDelete"
memberName := "test-add-member"

retrieveAccounts, err := keyValueStringToMap("RetrieveAccounts=true")
Expand All @@ -138,6 +123,7 @@ func TestAddRemoveSafeMemberSuccess(t *testing.T) {
MemberName: memberName,
SearchIn: "Vault",
Permissions: retrieveAccounts,
MemberType: "user",
}

err = client.AddSafeMember(safeName, addMember)
Expand All @@ -154,7 +140,7 @@ func TestAddRemoveSafeMemberSuccess(t *testing.T) {
func TestAddMemberInvalidMemberName(t *testing.T) {
client, err := defaultPASAPIClient(t)

safeName := "PasswordManager"
safeName := "TestCreateDelete"
memberName := "notReal"

retrieveAccounts, err := keyValueStringToMap("RetrieveAccounts=true")
Expand All @@ -166,6 +152,7 @@ func TestAddMemberInvalidMemberName(t *testing.T) {
MemberName: memberName,
SearchIn: "Vault",
Permissions: retrieveAccounts,
MemberType: "user",
}

err = client.AddSafeMember(safeName, addMember)
Expand All @@ -177,11 +164,31 @@ func TestAddMemberInvalidMemberName(t *testing.T) {
func TestRemoveMemberInvalidMemberName(t *testing.T) {
client, err := defaultPASAPIClient(t)

safeName := "PasswordManager"
safeName := "TestCreateDelete"
memberName := "notReal"

err = client.RemoveSafeMember(safeName, memberName)
if err == nil {
t.Errorf("Removed a non-existent member. This should not happen")
}
}

func TestRemoveSafeSuccess(t *testing.T) {
client, err := defaultPASAPIClient(t)

safeName := "TestCreateDelete"
err = client.DeleteSafe(safeName)
if err != nil {
t.Errorf("Failed to delete safe '%s' even though it should exist and should be deletable. %s", safeName, err)
}
}

func TestRemoveSafeFail(t *testing.T) {
client, err := defaultPASAPIClient(t)

safeName := "notRealSafeName"
err = client.DeleteSafe(safeName)
if err == nil {
t.Errorf("Client returned successful safe deletion even though safe '%s' should not exist", safeName)
}
}
2 changes: 1 addition & 1 deletion pkg/cybr/api/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
func (c Client) UnsuspendUser(userID int) error {
url := fmt.Sprintf("%s/passwordvault/api/Users/%d/activate", c.BaseURL, userID)

response, err := httpJson.Post(false, url, c.SessionToken, nil, c.InsecureTLS, c.Logger)
response, err := httpJson.Post(false, url, c.SessionToken, emptyBody, c.InsecureTLS, c.Logger)
if err != nil {
returnedError, _ := json.Marshal(response)
return fmt.Errorf("Failed to unsuspend user with id '%d'. %s. %s", userID, string(returnedError), err)
Expand Down
Loading

0 comments on commit 2d6d5ff

Please sign in to comment.