diff --git a/clients/rancher/auth/auth.go b/clients/rancher/auth/auth.go new file mode 100644 index 00000000..38e4dfe7 --- /dev/null +++ b/clients/rancher/auth/auth.go @@ -0,0 +1,23 @@ +package auth + +import ( + "github.com/rancher/shepherd/clients/rancher/auth/openldap" + management "github.com/rancher/shepherd/clients/rancher/generated/management/v3" + "github.com/rancher/shepherd/pkg/session" +) + +type Client struct { + OLDAP *openldap.OLDAPClient +} + +// NewAuth constructs the Auth Provider Struct +func NewClient(mgmt *management.Client, session *session.Session) (*Client, error) { + oLDAP, err := openldap.NewOLDAP(mgmt, session) + if err != nil { + return nil, err + } + + return &Client{ + OLDAP: oLDAP, + }, nil +} diff --git a/clients/rancher/auth/authprovider.go b/clients/rancher/auth/authprovider.go new file mode 100644 index 00000000..efd10f1c --- /dev/null +++ b/clients/rancher/auth/authprovider.go @@ -0,0 +1,13 @@ +package auth + +type Provider string + +const ( + LocalAuth Provider = "local" + OpenLDAPAuth Provider = "openLdap" +) + +// String stringer for the AuthProvider +func (a Provider) String() string { + return string(a) +} diff --git a/clients/rancher/auth/openldap/config.go b/clients/rancher/auth/openldap/config.go new file mode 100644 index 00000000..f30a0dfe --- /dev/null +++ b/clients/rancher/auth/openldap/config.go @@ -0,0 +1,34 @@ +package openldap + +const ( + ConfigurationFileKey = "openLDAP" +) + +type Config struct { + Hostname string `json:"hostname" yaml:"hostname"` + IP string `json:"IP" yaml:"IP"` + ServiceAccount *ServiceAccount `json:"serviceAccount" yaml:"serviceAccount"` + Group *Group `json:"group" yaml:"group"` + Users *Users `json:"users" yaml:"users"` + AccessMode string `json:"accessMode" yaml:"accessMode" default:"unrestricted"` +} + +type ServiceAccount struct { + DistinguishedName string `json:"distinguishedName" yaml:"distinguishedName"` + Password string `json:"password" yaml:"password"` +} + +type Group struct { + ObjectClass string `json:"objectClass" yaml:"objectClass"` + MemberMappingAttribute string `json:"memberMappingAttribute" yaml:"memberMappingAttribute"` +} + +type Users struct { + Admin *User `json:"admin" yaml:"admin"` + SearchBase string `json:"searchBase" yaml:"searchBase"` +} + +type User struct { + Password string `json:"password,omitempty" yaml:"password,omitempty"` + Username string `json:"username,omitempty" yaml:"username,omitempty"` +} diff --git a/extensions/auth/openldap/openldap.go b/clients/rancher/auth/openldap/openldap.go similarity index 63% rename from extensions/auth/openldap/openldap.go rename to clients/rancher/auth/openldap/openldap.go index 58ef0e99..2c5a9665 100644 --- a/extensions/auth/openldap/openldap.go +++ b/clients/rancher/auth/openldap/openldap.go @@ -3,8 +3,9 @@ package openldap import ( "fmt" - management "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" - "github.com/rancher/shepherd/clients/rancher" + apisv3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" + + management "github.com/rancher/shepherd/clients/rancher/generated/management/v3" "github.com/rancher/shepherd/pkg/config" "github.com/rancher/shepherd/pkg/session" ) @@ -12,6 +13,7 @@ import ( type OLDAPOperations interface { Enable() error Disable() error + Update(existing, updates *management.AuthConfig) (*management.AuthConfig, error) } const ( @@ -19,28 +21,28 @@ const ( schemaType = "openLdapConfigs" ) -type OLDAP struct { - Config *Config - +type OLDAPClient struct { + client *management.Client session *session.Session - client *rancher.Client + + Config *Config } // NewOLDAP constructs OLDAP struct after it reads Open LDAP from the configuration file -func NewOLDAP(client *rancher.Client, ts *session.Session) (*OLDAP, error) { +func NewOLDAP(client *management.Client, session *session.Session) (*OLDAPClient, error) { ldapConfig := new(Config) config.LoadConfig(ConfigurationFileKey, ldapConfig) - return &OLDAP{ - Config: ldapConfig, - session: ts, + return &OLDAPClient{ client: client, + session: session, + Config: ldapConfig, }, nil } // Enable is a method of OLDAP, makes a request to the action with the given // configuration values -func (o *OLDAP) Enable() error { +func (o *OLDAPClient) Enable() error { var jsonResp map[string]interface{} url := o.newActionURL("testAndApply") @@ -49,7 +51,7 @@ func (o *OLDAP) Enable() error { return err } - err = o.client.Management.Ops.DoModify("POST", url, enableActionInput, &jsonResp) + err = o.client.Ops.DoModify("POST", url, enableActionInput, &jsonResp) if err != nil { return err } @@ -61,28 +63,35 @@ func (o *OLDAP) Enable() error { return nil } +// Update is a method of OLDAP, makes an update with the given configuration values +func (o *OLDAPClient) Update( + existing, updates *management.AuthConfig, +) (*management.AuthConfig, error) { + return o.client.AuthConfig.Update(existing, updates) +} + // Disable is a method of OLDAP, makes a request to disable Open LDAP -func (o *OLDAP) Disable() error { +func (o *OLDAPClient) Disable() error { var jsonResp map[string]any url := o.newActionURL("disable") disableActionInput := o.newDisableInput() - return o.client.Management.Ops.DoModify("POST", url, &disableActionInput, &jsonResp) + return o.client.Ops.DoModify("POST", url, &disableActionInput, &jsonResp) } -func (o *OLDAP) newActionURL(action string) string { - protocol := "https" - - if *o.client.RancherConfig.Insecure { - protocol = "http" - } - - return fmt.Sprintf("%v://%v/v3/%v/%v?action=%v", protocol, o.client.RancherConfig.Host, schemaType, resourceType, action) +func (o *OLDAPClient) newActionURL(action string) string { + return fmt.Sprintf( + "%v/%v/%v?action=%v", + o.client.Opts.URL, + schemaType, + resourceType, + action, + ) } -func (o *OLDAP) newEnableInputFromConfig() (*management.LdapTestAndApplyInput, error) { - var resource management.LdapTestAndApplyInput +func (o *OLDAPClient) newEnableInputFromConfig() (*apisv3.LdapTestAndApplyInput, error) { + var resource apisv3.LdapTestAndApplyInput var server string if o.Config.Hostname == "" && o.Config.IP == "" { @@ -116,6 +125,6 @@ func (o *OLDAP) newEnableInputFromConfig() (*management.LdapTestAndApplyInput, e return &resource, nil } -func (o *OLDAP) newDisableInput() []byte { +func (o *OLDAPClient) newDisableInput() []byte { return []byte(`{"action": "disable"}`) } diff --git a/clients/rancher/client.go b/clients/rancher/client.go index 2e757774..be679d8e 100644 --- a/clients/rancher/client.go +++ b/clients/rancher/client.go @@ -11,13 +11,20 @@ import ( "github.com/pkg/errors" "github.com/rancher/norman/httperror" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + frameworkDynamic "github.com/rancher/shepherd/clients/dynamic" "github.com/rancher/shepherd/clients/ec2" + kubeProvisioning "github.com/rancher/shepherd/clients/provisioning" + "github.com/rancher/shepherd/clients/rancher/auth" "github.com/rancher/shepherd/clients/rancher/catalog" management "github.com/rancher/shepherd/clients/rancher/generated/management/v3" v1 "github.com/rancher/shepherd/clients/rancher/v1" - - kubeProvisioning "github.com/rancher/shepherd/clients/provisioning" "github.com/rancher/shepherd/clients/ranchercli" kubeRKE "github.com/rancher/shepherd/clients/rke" "github.com/rancher/shepherd/pkg/clientbase" @@ -25,12 +32,6 @@ import ( "github.com/rancher/shepherd/pkg/environmentflag" "github.com/rancher/shepherd/pkg/session" "github.com/rancher/shepherd/pkg/wrangler" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" ) // Client is the main rancher Client object that gives an end user access to the Provisioning and Management @@ -49,6 +50,7 @@ type Client struct { // CLI is the client used to interact with the Rancher CLI CLI *ranchercli.Client // Session is the session object used by the client to track all the resources being created by the client. + Auth *auth.Client // Session is the session object used by the client to track all the resources being created by the client. Session *session.Session // Flags is the environment flags used by the client to test selectively against a rancher instance. Flags *environmentflag.EnvironmentFlags @@ -137,6 +139,13 @@ func newClient(c *Client, bearerToken string, config *Config, session *session.S c.WranglerContext = wranglerContext + auth, err := auth.NewClient(c.Management, session) + if err != nil { + return nil, err + } + + c.Auth = auth + splitBearerKey := strings.Split(bearerToken, ":") token, err := c.Management.Token.ByID(splitBearerKey[0]) if err != nil { @@ -220,7 +229,18 @@ func (c *Client) doAction(endpoint, action string, body []byte, output interface // AsUser accepts a user object, and then creates a token for said `user`. Then it instantiates and returns a Client using the token created. // This function uses the login action, and user must have a correct username and password combination. func (c *Client) AsUser(user *management.User) (*Client, error) { - returnedToken, err := c.login(user) + returnedToken, err := c.login(user, auth.LocalAuth) + if err != nil { + return nil, err + } + + return NewClient(returnedToken.Token, c.Session) +} + +// AsAuthUser accepts a user object, and then creates a token for said `user`. Then it instantiates and returns a Client using the token created. +// This function uses the login action, and user must have a correct username and password combination. +func (c *Client) AsAuthUser(user *management.User, authProvider auth.Provider) (*Client, error) { + returnedToken, err := c.login(user, authProvider) if err != nil { return nil, err } @@ -346,7 +366,7 @@ func (c *Client) GetManagementWatchInterface(schemaType string, opts metav1.List } // login uses the local authentication provider to authenticate a user and return the subsequent token. -func (c *Client) login(user *management.User) (*management.Token, error) { +func (c *Client) login(user *management.User, provider auth.Provider) (*management.Token, error) { token := &management.Token{} bodyContent, err := json.Marshal(struct { Username string `json:"username"` @@ -358,7 +378,8 @@ func (c *Client) login(user *management.User) (*management.Token, error) { if err != nil { return nil, err } - err = c.doAction("/v3-public/localProviders/local", "login", bodyContent, token) + endpoint := fmt.Sprintf("/v3-public/%vProviders/%v", provider.String(), strings.ToLower(provider.String())) + err = c.doAction(endpoint, "login", bodyContent, token) if err != nil { return nil, err } diff --git a/extensions/auth/auth.go b/extensions/auth/auth.go deleted file mode 100644 index 85b32ce2..00000000 --- a/extensions/auth/auth.go +++ /dev/null @@ -1,23 +0,0 @@ -package auth - -import ( - "github.com/rancher/shepherd/clients/rancher" - "github.com/rancher/shepherd/extensions/auth/openldap" - "github.com/rancher/shepherd/pkg/session" -) - -type Auth struct { - OLDAP *openldap.OLDAP -} - -// NewAuth constructs the Auth Provider Struct -func NewAuth(client *rancher.Client, session *session.Session) (*Auth, error) { - oLDAP, err := openldap.NewOLDAP(client, session) - if err != nil { - return nil, err - } - - return &Auth{ - OLDAP: oLDAP, - }, nil -} diff --git a/extensions/auth/openldap/config.go b/extensions/auth/openldap/config.go deleted file mode 100644 index 52e08cfb..00000000 --- a/extensions/auth/openldap/config.go +++ /dev/null @@ -1,35 +0,0 @@ -package openldap - -const ( - ConfigurationFileKey = "openLDAP" -) - -type Config struct { - Hostname string `json:"hostname" yaml:"hostname"` - IP string `json:"IP" yaml:"IP"` - ServiceAccount *ServiceAccount `json:"serviceAccount" yaml:"serviceAccount"` - Group *Group `json:"group" yaml:"group"` - Users *Users `json:"users" yaml:"users"` - AccessMode string `json:"accessMode" yaml:"accessMode" default:"unrestricted"` -} - -type ServiceAccount struct { - DistinguishedName string `json:"distinguishedName" yaml:"distinguishedName"` - Password string `json:"password" yaml:"password"` -} - -type Group struct { - ObjectClass string `json:"objectClass" yaml:"objectClass"` - MemberMappingAttribute string `json:"memberMappingAttribute" yaml:"memberMappingAttribute"` -} - -type User struct { - Username string `json:"username" yaml:"username"` - Password string `json:"password" yaml:"password"` -} - -type Users struct { - Admin *User `json:"admin" yaml:"admin"` - SearchBase string `json:"searchBase" yaml:"searchBase"` - Others map[string]User `json:"others" yaml:"others"` -} diff --git a/extensions/users/users.go b/extensions/users/users.go index 6dc923f3..668b1597 100644 --- a/extensions/users/users.go +++ b/extensions/users/users.go @@ -49,6 +49,22 @@ func UserConfig() (user *management.User) { return } +// RefreshGroupMembership is helper function that sends a POST request to user action refresh auth provider access +func RefreshGroupMembership(client *rancher.Client) error { + endpoint := fmt.Sprintf("https://%v/v3/%v?action=%v", client.RancherConfig.Host, "users", "refreshauthprovideraccess") + + var jsonResp map[string]any + + bodyContent := []byte(`{}`) + + err := client.Management.Ops.DoModify("POST", endpoint, &bodyContent, &jsonResp) + if err != nil { + return err + } + + return nil +} + // CreateUserWithRole is helper function that creates a user with a role or multiple roles func CreateUserWithRole(rancherClient *rancher.Client, user *management.User, roles ...string) (*management.User, error) { createdUser, err := rancherClient.Management.User.Create(user) @@ -76,9 +92,7 @@ func CreateUserWithRole(rancherClient *rancher.Client, user *management.User, ro // AddProjectMember is a helper function that adds a project role to `user`. It uses the watch.WatchWait to ensure BackingNamespaceCreated is true. // If a list of ResourceAttributes is given, then the function blocks until all // attributes are allowed by SelfSubjectAccessReviews OR the function times out. -func AddProjectMember(rancherClient *rancher.Client, project *management.Project, - user *management.User, projectRole string, attrs []*authzv1.ResourceAttributes, -) error { +func AddProjectMember(rancherClient *rancher.Client, project *management.Project, user *management.User, projectRole string, attrs []*authzv1.ResourceAttributes) error { role := &management.ProjectRoleTemplateBinding{ ProjectID: project.ID, UserPrincipalID: user.PrincipalIDs[0], @@ -189,9 +203,7 @@ func RemoveProjectMember(rancherClient *rancher.Client, user *management.User) e // AddClusterRoleToUser is a helper function that adds a cluster role to `user`. // If a list of ResourceAttributes is given, then the function blocks until all // attributes are allowed by SelfSubjectAccessReviews OR the function times out. -func AddClusterRoleToUser(rancherClient *rancher.Client, cluster *management.Cluster, - user *management.User, clusterRole string, attrs []*authzv1.ResourceAttributes, -) error { +func AddClusterRoleToUser(rancherClient *rancher.Client, cluster *management.Cluster, user *management.User, clusterRole string, attrs []*authzv1.ResourceAttributes) error { role := &management.ClusterRoleTemplateBinding{ ClusterID: cluster.Resource.ID, UserPrincipalID: user.PrincipalIDs[0],