-
Notifications
You must be signed in to change notification settings - Fork 112
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #331 from AlexVulaj/banned-users-script
Add commands to check if cluster owner is a banned user, and to verify that their pull secret matches the cluster's
- Loading branch information
Showing
4 changed files
with
232 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package cluster | ||
|
||
import ( | ||
"fmt" | ||
"github.com/openshift/osdctl/pkg/utils" | ||
"github.com/spf13/cobra" | ||
cmdutil "k8s.io/kubectl/pkg/cmd/util" | ||
) | ||
|
||
const BanCodeExportControlCompliance = "export_control_compliance" | ||
|
||
func newCmdCheckBannedUser() *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "check-banned-user [CLUSTER_ID]", | ||
Short: "Checks if the cluster owner is a banned user.", | ||
Args: cobra.ExactArgs(1), | ||
DisableAutoGenTag: true, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
cmdutil.CheckErr(CheckBannedUser(args[0])) | ||
}, | ||
} | ||
} | ||
|
||
func CheckBannedUser(clusterID string) error { | ||
ocm := utils.CreateConnection() | ||
defer func() { | ||
if ocmCloseErr := ocm.Close(); ocmCloseErr != nil { | ||
fmt.Printf("Cannot close the ocm (possible memory leak): %q", ocmCloseErr) | ||
} | ||
}() | ||
|
||
fmt.Print("Finding subscription account: ") | ||
subscription, err := utils.GetSubscription(ocm, clusterID) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if status := subscription.Status(); status != "Active" { | ||
return fmt.Errorf("Expecting status 'Active' found %v\n", status) | ||
} | ||
|
||
fmt.Printf("Account %v - %v - %v\n", subscription.SupportLevel(), subscription.Creator().HREF(), subscription.Status()) | ||
|
||
fmt.Print("Finding account owner: ") | ||
creator, err := utils.GetAccount(ocm, subscription.Creator().ID()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
userEmail := creator.Email() | ||
userBanned := creator.Banned() | ||
userBanCode := creator.BanCode() | ||
userBanDescription := creator.BanDescription() | ||
lastUpdate := creator.UpdatedAt() | ||
|
||
fmt.Printf("%v\n-------------------\nLast Update : %v\n", userEmail, lastUpdate) | ||
|
||
if userBanned { | ||
fmt.Println("User is banned") | ||
fmt.Printf("Ban code = %v\n", userBanCode) | ||
fmt.Printf("Ban description = %v\n", userBanDescription) | ||
if userBanCode == BanCodeExportControlCompliance { | ||
fmt.Println("User banned due to export control compliance.\nPlease follow the steps detailed here: https://github.com/openshift/ops-sop/blob/master/v4/alerts/UpgradeConfigSyncFailureOver4HrSRE.md#user-banneddisabled-due-to-export-control-compliance .") | ||
return nil | ||
} | ||
fmt.Println("Recommend sending service log with:") | ||
fmt.Printf("osdctl servicelog post -t https://raw.githubusercontent.com/openshift/managed-notifications/master/ocm/cluster_owner_disabled.json %v\n", clusterID) | ||
return nil | ||
} | ||
fmt.Println("User allowed") | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
package cluster | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
v1 "github.com/openshift-online/ocm-sdk-go/accountsmgmt/v1" | ||
"github.com/openshift/osdctl/pkg/utils" | ||
"github.com/spf13/cobra" | ||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/types" | ||
"k8s.io/cli-runtime/pkg/genericclioptions" | ||
cmdutil "k8s.io/kubectl/pkg/cmd/util" | ||
"sigs.k8s.io/controller-runtime/pkg/client" | ||
) | ||
|
||
var BackplaneClusterAdmin = "backplane-cluster-admin" | ||
|
||
func newCmdValidatePullSecret(kubeCli client.Client, flags *genericclioptions.ConfigFlags) *cobra.Command { | ||
return &cobra.Command{ | ||
Use: "validate-pull-secret [CLUSTER_ID]", | ||
Short: "Checks if the pull secret email matches the owner email", | ||
Long: `Checks if the pull secret email matches the owner email. | ||
The owner's email to check will be determined by the cluster identifier passed to the command, while the pull secret checked will be determined by the cluster that the caller is currently logged in to.`, | ||
Args: cobra.ExactArgs(1), | ||
DisableAutoGenTag: true, | ||
Run: func(cmd *cobra.Command, args []string) { | ||
cmdutil.CheckErr(ValidatePullSecret(args[0], kubeCli, flags)) | ||
}, | ||
} | ||
} | ||
|
||
func ValidatePullSecret(clusterID string, kubeCli client.Client, flags *genericclioptions.ConfigFlags) error { | ||
ocm := utils.CreateConnection() | ||
defer func() { | ||
if ocmCloseErr := ocm.Close(); ocmCloseErr != nil { | ||
fmt.Printf("Cannot close the ocm (possible memory leak): %q", ocmCloseErr) | ||
} | ||
}() | ||
|
||
fmt.Println("Checking if pull secret email matches user email") | ||
|
||
// This is the flagset for the kubeCli object provided from the root command. Set here to retroactively impersonate backplane-cluster-admin | ||
flags.Impersonate = &BackplaneClusterAdmin | ||
secret := &corev1.Secret{} | ||
err := kubeCli.Get(context.TODO(), types.NamespacedName{Namespace: "openshift-config", Name: "pull-secret"}, secret) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
clusterPullSecretEmail, err, done := getPullSecretEmail(clusterID, secret) | ||
if done { | ||
return err | ||
} | ||
|
||
subscription, err := utils.GetSubscription(ocm, clusterID) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
account, err := utils.GetAccount(ocm, subscription.Creator().ID()) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if account.Email() != clusterPullSecretEmail { | ||
fmt.Println("Pull secret email doesn't match OCM user email. Recommend sending service log with:") | ||
fmt.Printf("osdctl servicelog post -t https://raw.githubusercontent.com/openshift/managed-notifications/master/osd/pull_secret_user_mismatch.json %v\n", clusterID) | ||
return nil | ||
} | ||
|
||
fmt.Println("Email addresses match.") | ||
return nil | ||
} | ||
|
||
func getPullSecretEmail(clusterID string, secret *corev1.Secret) (string, error, bool) { | ||
dockerConfigJsonBytes, found := secret.Data[".dockerconfigjson"] | ||
if !found { | ||
// Indicates issue w/ pull-secret, so we can stop evaluating and specify a more direct course of action | ||
fmt.Println("Secret does not contain expected key '.dockerconfigjson'. Recommend sending a service log with the following command:") | ||
fmt.Printf("osdctl servicelog post -t https://raw.githubusercontent.com/openshift/managed-notifications/master/osd/pull_secret_change_breaking_upgradesync.json %v\n", clusterID) | ||
return "", nil, true | ||
} | ||
|
||
dockerConfigJson, err := v1.UnmarshalAccessToken(dockerConfigJsonBytes) | ||
if err != nil { | ||
return "", err, true | ||
} | ||
|
||
cloudOpenshiftAuth, found := dockerConfigJson.Auths()["cloud.openshift.com"] | ||
if !found { | ||
fmt.Println("Secret does not contain entry for cloud.openshift.com. Recommend sending a service log with the following command:") | ||
fmt.Printf("osdctl servicelog post -t https://raw.githubusercontent.com/openshift/managed-notifications/master/osd/pull_secret_change_breaking_upgradesync.json %v\n", clusterID) | ||
return "", nil, true | ||
} | ||
|
||
clusterPullSecretEmail := cloudOpenshiftAuth.Email() | ||
if clusterPullSecretEmail == "" { | ||
fmt.Printf("%v\n%v\n%v\n", | ||
"Couldn't extract email address from pull secret for cloud.openshift.com", | ||
"This can mean the pull secret is misconfigured. Please verify the pull secret manually:", | ||
" oc get secret -n openshift-config pull-secret -o json | jq -r '.data[\".dockerconfigjson\"]' | base64 -d") | ||
return "", nil, true | ||
} | ||
return clusterPullSecretEmail, nil, false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package cluster | ||
|
||
import ( | ||
corev1 "k8s.io/api/core/v1" | ||
"reflect" | ||
"testing" | ||
) | ||
|
||
func Test_getPullSecretEmail(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
secret *corev1.Secret | ||
expectedEmail string | ||
expectedError error | ||
expectedDone bool | ||
}{ | ||
{ | ||
name: "Missing dockerconfigjson", | ||
secret: &corev1.Secret{Data: map[string][]byte{}}, | ||
expectedDone: true, | ||
}, | ||
{ | ||
name: "Missing cloud.openshift.com auth", | ||
secret: &corev1.Secret{Data: map[string][]byte{".dockerconfigjson": []byte("{\"auths\":{}}")}}, | ||
expectedDone: true, | ||
}, | ||
{ | ||
name: "Missing email", | ||
secret: &corev1.Secret{Data: map[string][]byte{".dockerconfigjson": []byte("{\"auths\":{\"cloud.openshift.com\":{}}}")}}, | ||
expectedDone: true, | ||
}, | ||
{ | ||
name: "Valid pull secret", | ||
secret: &corev1.Secret{Data: map[string][]byte{".dockerconfigjson": []byte("{\"auths\":{\"cloud.openshift.com\":{\"email\":\"[email protected]\"}}}")}}, | ||
expectedEmail: "[email protected]", | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
email, err, done := getPullSecretEmail("abc123", tt.secret) | ||
if email != tt.expectedEmail { | ||
t.Errorf("getPullSecretEmail() email = %v, expectedEmail %v", email, tt.expectedEmail) | ||
} | ||
if !reflect.DeepEqual(err, tt.expectedError) { | ||
t.Errorf("getPullSecretEmail() err = %v, expectedEmail %v", err, tt.expectedError) | ||
} | ||
if done != tt.expectedDone { | ||
t.Errorf("getPullSecretEmail() done = %v, expectedEmail %v", done, tt.expectedDone) | ||
} | ||
}) | ||
} | ||
} |