Skip to content

Commit

Permalink
OCM-7647 | test: automate id:OCP-72899 create/list/describe/delete br…
Browse files Browse the repository at this point in the history
…eak_glass_credentials
  • Loading branch information
aaraj7 committed Apr 26, 2024
1 parent 796b595 commit 46b5302
Show file tree
Hide file tree
Showing 4 changed files with 326 additions and 11 deletions.
97 changes: 97 additions & 0 deletions tests/e2e/hcp_external_auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package e2e

import (
"time"

. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"

"github.com/openshift/rosa/tests/ci/labels"
"github.com/openshift/rosa/tests/utils/common"
"github.com/openshift/rosa/tests/utils/exec/rosacli"
)

var _ = Describe("Create Tuning Config", func() {

var rosaClient *rosacli.Client
var clusterService rosacli.ClusterService

BeforeEach(func() {
Expect(clusterID).ToNot(BeEmpty(), "Cluster ID is empty, please export the env variable CLUSTER_ID")
rosaClient = rosacli.NewClient()
clusterService = rosaClient.Cluster

By("Check cluster is hosted cluster and external auth config is enabled")
hosted, err := clusterService.IsHostedCPCluster(clusterID)
Expect(err).ToNot(HaveOccurred())
externalAuthProvider, err := clusterService.IsExternalAuthenticationEnabled(clusterID)
Expect(err).ToNot(HaveOccurred())
if !hosted || !externalAuthProvider {
Skip("This is only for external_auth_config enabled hypershift cluster")
}
})

AfterEach(func() {
By("Clean remaining resources")
err := rosaClient.CleanResources(clusterID)
Expect(err).ToNot(HaveOccurred())
})

It("create/list/describe/delete HCP cluster with break_glass_credentials via Rosa client can work well - [id:72899]",
labels.High, labels.NonClassicCluster,
func() {
By("Retrieve help for create/list/describe/delete break-glass-credential")
_, err := rosaClient.BreakGlassCredential.RetrieveHelpForCreate()
Expect(err).ToNot(HaveOccurred())
_, err = rosaClient.BreakGlassCredential.RetrieveHelpForDescribe()
Expect(err).ToNot(HaveOccurred())
_, err = rosaClient.BreakGlassCredential.RetrieveHelpForList()
Expect(err).ToNot(HaveOccurred())
_, err = rosaClient.BreakGlassCredential.RetrieveHelpForDelete()
Expect(err).ToNot(HaveOccurred())

By("Create a break-glass-credential to the cluster")
userName := common.GenerateRandomName("bgc-username", 2)
createTime := time.Now().UTC()
expiredTime := createTime.Add(2 * time.Hour).Format("Jan _2 2006 15:04 MST")

resp, err := rosaClient.BreakGlassCredential.CreateBreakGlassCredential(clusterID, userName, "--expiration", "2h")
Expect(err).ToNot(HaveOccurred())
textData := rosaClient.Parser.TextData.Input(resp).Parse().Tip()
Expect(textData).To(ContainSubstring("INFO: Successfully created a break glass credential for cluster '%s'", clusterID))

By("List the break-glass-credentials of the cluster")
breakGlassCredList, err := rosaClient.BreakGlassCredential.ListBreakGlassCredentialsAndReflect(clusterID)
Expect(err).ToNot(HaveOccurred())
isPresent, breakGlassCredential := breakGlassCredList.IsPresent(userName)
Expect(isPresent).To(BeTrue())
Eventually(rosaClient.BreakGlassCredential.WaitForBreakGlassCredentialToStatus(clusterID, "issued", userName), time.Minute*2, time.Second*10).Should(BeTrue())

By("Retrieve the break-glass-credential id")
breakGlassCredID := breakGlassCredential.ID

By("Describe the break-glass-credential")
output, err := rosaClient.BreakGlassCredential.DescribeBreakGlassCredentialsAndReflect(clusterID, breakGlassCredID)
Expect(err).ToNot(HaveOccurred())
// expiration timestamp changes from 4 to 5 seconds ahead, so have to remove second from time stamp
expirationTime, err := time.Parse("Jan _2 2006 15:04:05 MST", output.ExpireAt)
Expect(err).ToNot(HaveOccurred())
Expect(output.ID).To(Equal(breakGlassCredID))
Expect(expirationTime.Format("Jan _2 2006 15:04 MST")).To(Equal(expiredTime))
Expect(output.Username).To(Equal(userName))
Expect(output.Status).To(Equal("issued"))

By("Get the issued credential")
_, err = rosaClient.BreakGlassCredential.GetIssuedCredential(clusterID, breakGlassCredID)
Expect(err).ToNot(HaveOccurred())

By("Delete the break-glass-credential")
resp, err = rosaClient.BreakGlassCredential.DeleteBreakGlassCredential(clusterID)
Expect(err).ToNot(HaveOccurred())
textData = rosaClient.Parser.TextData.Input(resp).Parse().Tip()
Expect(textData).To(ContainSubstring("INFO: Successfully requested revocation for all break glass credentials from cluster '%s'", clusterID))

By("Check the break-glass-credential status is revoked")
Eventually(rosaClient.BreakGlassCredential.WaitForBreakGlassCredentialToStatus(clusterID, "revoked", userName), time.Minute*4, time.Second*10).Should(BeTrue())
})
})
204 changes: 204 additions & 0 deletions tests/utils/exec/rosacli/break_glass_credential_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package rosacli

import (
"bytes"

"gopkg.in/yaml.v3"
"k8s.io/apimachinery/pkg/util/wait"
. "github.com/openshift/rosa/tests/utils/log"
)

type BreakGlassCredentialService interface {
ResourcesCleaner

CreateBreakGlassCredential(clusterID string, userName string, flags ...string) (bytes.Buffer, error)
DeleteBreakGlassCredential(clusterID string) (bytes.Buffer, error)
ListBreakGlassCredentials(clusterID string) (bytes.Buffer, error)
ReflectBreakGlassCredentialLists(result bytes.Buffer) (bgcl *BreakGlassCredentialList, err error)
DescribeBreakGlassCredential(clusterID string, bgcID string) (bytes.Buffer, error)
ReflectBreakGlassCredentialDescription(result bytes.Buffer) (bgcd *BreakGlassCredentialDescription, err error)
GetIssuedCredential(clusterID string, bgcID string) (bytes.Buffer, error)
WaitForBreakGlassCredentialToStatus(clusterID string, status string, userName string) wait.ConditionFunc

ListBreakGlassCredentialsAndReflect(clusterID string) (*BreakGlassCredentialList, error)
DescribeBreakGlassCredentialsAndReflect(clusterID string, bgcID string) (*BreakGlassCredentialDescription, error)

RetrieveHelpForCreate() (bytes.Buffer, error)
RetrieveHelpForList() (bytes.Buffer, error)
RetrieveHelpForDescribe() (bytes.Buffer, error)
RetrieveHelpForDelete() (bytes.Buffer, error)
}

type breakglasscredentialService struct {
ResourcesService
}

type BreakGlassCredential struct {
ID string `json:"ID,omitempty"`
Username string `json:"Username,omitempty"`
Status string `json:"Status,omitempty"`
}

type BreakGlassCredentialList struct {
BreakGlassCredentials []BreakGlassCredential `json:"BreakGlassCredentials,omitempty"`
}

type BreakGlassCredentialDescription struct {
ID string `yaml:"ID,omitempty"`
Username string `yaml:"Username,omitempty"`
ExpireAt string `yaml:"Expire at,omitempty"`
Status string `yaml:"Status,omitempty"`
}

func NewBreakGlassCredentialService(client *Client) BreakGlassCredentialService {
return &breakglasscredentialService{
ResourcesService: ResourcesService{
client: client,
},
}
}

// Create BreakGlassCredential
func (b *breakglasscredentialService) CreateBreakGlassCredential(clusterID string, userName string, flags ...string) (output bytes.Buffer, err error) {
output, err = b.client.Runner.
Cmd("create", "break-glass-credential").
CmdFlags(append(flags, "-c", clusterID, "--username", userName)...).
Run()
return
}

// List BreakGlassCredentials
func (b *breakglasscredentialService) ListBreakGlassCredentials(clusterID string) (output bytes.Buffer, err error) {
output, err = b.client.Runner.
Cmd("list", "break-glass-credential").
CmdFlags("-c", clusterID).
Run()
return
}

// Check the breakGlassCredential with the userName exists in the breakGlassCredentialsList
func (breakglassCredList BreakGlassCredentialList) IsPresent(userName string) (existed bool, breakGlassCredential *BreakGlassCredential) {
existed = false
breakGlassCredential = &BreakGlassCredential{}
for _, breakGlassCred := range breakglassCredList.BreakGlassCredentials {
if breakGlassCred.Username == userName {
existed = true
breakGlassCredential = &breakGlassCred
break
}
}
return
}

func (b *breakglasscredentialService) ReflectBreakGlassCredentialLists(result bytes.Buffer) (bgcl *BreakGlassCredentialList, err error) {
bgcl = &BreakGlassCredentialList{}
theMap := b.client.Parser.TableData.Input(result).Parse().Output()
for _, bgcItem := range theMap {
breakGlassCredential := &BreakGlassCredential{}
err = MapStructure(bgcItem, breakGlassCredential)
if err != nil {
return
}
bgcl.BreakGlassCredentials = append(bgcl.BreakGlassCredentials, *breakGlassCredential)
}
return
}

func (b *breakglasscredentialService) ListBreakGlassCredentialsAndReflect(clusterID string) (*BreakGlassCredentialList, error) {
output, err := b.ListBreakGlassCredentials(clusterID)
if err != nil {
return nil, err
}
return b.ReflectBreakGlassCredentialLists(output)
}

// Describe BreakGlassCredential
func (b *breakglasscredentialService) DescribeBreakGlassCredential(clusterID string, bgcID string) (bytes.Buffer, error) {
describe := b.client.Runner.
Cmd("describe", "break-glass-credential", bgcID).
CmdFlags("-c", clusterID)

return describe.Run()
}

func (b *breakglasscredentialService) DescribeBreakGlassCredentialsAndReflect(clusterID string, bgcID string) (*BreakGlassCredentialDescription, error) {
output, err := b.DescribeBreakGlassCredential(clusterID, bgcID)
if err != nil {
return nil, err
}
return b.ReflectBreakGlassCredentialDescription(output)
}

func (b *breakglasscredentialService) ReflectBreakGlassCredentialDescription(result bytes.Buffer) (bgcd *BreakGlassCredentialDescription, err error) {
var data []byte
res := &BreakGlassCredentialDescription{}
theMap, err := b.client.Parser.TextData.Input(result).Parse().YamlToMap()
if err != nil {
return
}
data, err = yaml.Marshal(&theMap)
if err != nil {
return
}
err = yaml.Unmarshal(data, res)
return res, err
}

// Delete BreakGlassCredential
func (b *breakglasscredentialService) DeleteBreakGlassCredential(clusterID string) (output bytes.Buffer, err error) {
output, err = b.client.Runner.
Cmd("revoke", "break-glass-credential").
CmdFlags("-c", clusterID, "-y").
Run()
return
}

func (b *breakglasscredentialService) GetIssuedCredential(clusterID string, bgcID string) (bytes.Buffer, error) {
output, err := b.client.Runner.
Cmd("describe", "break-glass-credential").
CmdFlags("-c", clusterID, "--id", bgcID, "--kubeconfig").
Run()
return output, err
}

func (b *breakglasscredentialService) WaitForBreakGlassCredentialToStatus(clusterID string, status string, userName string) wait.ConditionFunc {
return func() (bool, error) {
breakGlassCredList, err := b.ListBreakGlassCredentialsAndReflect(clusterID)
if err != nil {
return false, err
}
_, breakGlassCredential := breakGlassCredList.IsPresent(userName)
Logger.Infof("The status for break-glass-credential %s is %s\n", breakGlassCredential.ID, breakGlassCredential.Status)
return breakGlassCredential.Status == status, err
}
}

// Help for Create BreakGlassCredential
func (b *breakglasscredentialService) RetrieveHelpForCreate() (output bytes.Buffer, err error) {
return b.client.Runner.Cmd("create", "break-glass-credential").CmdFlags("-h").Run()
}

// Help for List BreakGlassCredential
func (b *breakglasscredentialService) RetrieveHelpForList() (output bytes.Buffer, err error) {
return b.client.Runner.Cmd("list", "break-glass-credential").CmdFlags("-h").Run()
}

// Help for Describe BreakGlassCredential
func (b *breakglasscredentialService) RetrieveHelpForDescribe() (output bytes.Buffer, err error) {
return b.client.Runner.Cmd("describe", "break-glass-credential").CmdFlags("-h").Run()
}

// Help for Delete BreakGlassCredential
func (b *breakglasscredentialService) RetrieveHelpForDelete() (output bytes.Buffer, err error) {
return b.client.Runner.Cmd("revoke", "break-glass-credential").CmdFlags("-h").Run()
}

func (b *breakglasscredentialService) CleanResources(clusterID string) (errors []error) {
Logger.Infof("Remove remaining break-glass-credentials")
_, err := b.DeleteBreakGlassCredential(clusterID)
if err != nil {
errors = append(errors, err)
}

return
}
11 changes: 11 additions & 0 deletions tests/utils/exec/rosacli/cluster_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ type ClusterService interface {
IsUsingReusableOIDCConfig(clusterID string) (bool, error)
GetClusterVersion(clusterID string) (config.Version, error)
IsBYOVPCCluster(clusterID string) (bool, error)
IsExternalAuthenticationEnabled(clusterID string) (bool, error)
}

type clusterService struct {
Expand Down Expand Up @@ -89,6 +90,7 @@ type ClusterDescription struct {
LimitedSupport []map[string]string `yaml:"Limited Support,omitempty"`
AuditLogRoleARN string `yaml:"Audit Log Role ARN,omitempty"`
FailedInflightChecks string `yaml:"Failed Inflight Checks,omitempty"`
ExternalAuthentication string `yaml:"External Authentication, omitempty"`
}

func (c *clusterService) DescribeCluster(clusterID string) (bytes.Buffer, error) {
Expand Down Expand Up @@ -286,3 +288,12 @@ func RetrieveDesiredComputeNodes(clusterDescription *ClusterDescription) (nodesN
}
return
}

// Check if the cluster is external authentication enabled cluster
func (c *clusterService) IsExternalAuthenticationEnabled(clusterID string) (bool, error) {
jsonData, err := c.getJSONClusterDescription(clusterID)
if err != nil {
return false, err
}
return jsonData.DigBool("external_auth_config", "enabled"), nil
}
25 changes: 14 additions & 11 deletions tests/utils/exec/rosacli/cmd_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,18 @@ type Client struct {

// services
// Keep in alphabetical order
Cluster ClusterService
IDP IDPService
Ingress IngressService
KubeletConfig KubeletConfigService
MachinePool MachinePoolService
MachinePoolUpgrade MachinePoolUpgradeService
NetworkVerifier NetworkVerifierService
OCMResource OCMResourceService
TuningConfig TuningConfigService
User UserService
Version VersionService
Cluster ClusterService
IDP IDPService
Ingress IngressService
KubeletConfig KubeletConfigService
MachinePool MachinePoolService
MachinePoolUpgrade MachinePoolUpgradeService
NetworkVerifier NetworkVerifierService
OCMResource OCMResourceService
TuningConfig TuningConfigService
User UserService
Version VersionService
BreakGlassCredential BreakGlassCredentialService
}

func NewClient() *Client {
Expand All @@ -64,6 +65,7 @@ func NewClient() *Client {
client.TuningConfig = NewTuningConfigService(client)
client.User = NewUserService(client)
client.Version = NewVersionService(client)
client.BreakGlassCredential = NewBreakGlassCredentialService(client)

return client
}
Expand All @@ -89,6 +91,7 @@ func (c *Client) CleanResources(clusterID string) error {
errorList = append(errorList, c.IDP.CleanResources(clusterID)...)
errorList = append(errorList, c.OCMResource.CleanResources(clusterID)...)
errorList = append(errorList, c.Cluster.CleanResources(clusterID)...)
errorList = append(errorList, c.BreakGlassCredential.CleanResources(clusterID)...)

return errors.Join(errorList...)

Expand Down

0 comments on commit 46b5302

Please sign in to comment.