Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope various client requests to a project #44

Merged
merged 1 commit into from
Aug 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions charts/region/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ description: A Helm chart for deploying Unikorn's Region Controller

type: application

version: v0.1.31
appVersion: v0.1.31
version: v0.1.32
appVersion: v0.1.32

icon: https://raw.githubusercontent.com/unikorn-cloud/assets/main/images/logos/dark-on-light/icon.png

Expand Down
35 changes: 26 additions & 9 deletions pkg/providers/openstack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ Start by selecting a unique name that will be used for the deployment's name, pr
```bash
export USER=unikorn-staging
export DOMAIN=unikorn-staging
export PROJECT=unikorn-default
yankcrime marked this conversation as resolved.
Show resolved Hide resolved
export PASSWORD=$(apg -n 1 -m 24)
```

Create the domain.
#### Create the domain.
The use of project domains for projects deployed to provision Kubernetes cluster achieves a few aims.
First namespace isolation.
Second is a security consideration.
Expand All @@ -32,31 +33,45 @@ A domain may also aid in simplifying operations like auditing and capacity plann
DOMAIN_ID=$(openstack domain create ${DOMAIN} -f json | jq -r .id)
```

Crete the user.
#### Create the project.
As the OpenStack provider for the region controller also functions as a client in order to retrieve information such as available images, flavors, and so on it also needs to be associated with a project so that the default policy for various API requests is correctly satisfied:

```bash
PROJECT_ID=$(openstack project create $PROJECT --domain $DOMAIN -f json | jq -r .id)
```

#### Create the user.

```bash
USER_ID=$(openstack user create --domain ${DOMAIN_ID} --password ${PASSWORD} ${USER} -f json | jq -r .id)
```

Grant any roles to the user.
### Grant any roles to the user.
When a Kubernetes cluster is provisioned, it will be done using application credentials, so ensure any required application credentials as configured for the region are explicitly associated with the user here.

```bash
for role in _member_ member load-balancer_member manager; do
for role in member load-balancer_member manager; do
openstack role add --user ${USER_ID} --domain ${DOMAIN_ID} ${role}
done
```

And also grant the `member` role on the project we created in a previous step:

```bash
openstack role add --user ${USER_ID} --project ${PROJECT_ID} member
```

### Unikorn Configuration

When we create a `Region` of type `openstack`, it will require a secret that contains credentials.
This can be configured as follows.

```bash
kubectl create secret generic -n unikorn-region gb-north-1-credentials \
--from-literal=domain-id=${DOMAIN_ID} \
--from-literal=user-id=${USER_ID} \
--from-literal=password=${PASSWORD}
--from-literal=domain-id=${DOMAIN_ID} \
--from-literal=project-id=${PROJECT_ID} \
--from-literal=user-id=${USER_ID} \
--from-literal=password=${PASSWORD}
```

Finally we can create the region itself, although this should be statically configured via Helm.
Expand All @@ -83,9 +98,11 @@ spec:
Cleanup actions.

```bash
unset DOMAIN
unset DOMAIN_ID
unset USER
unset USER_ID
unset PASSWORD
unset DOMAIN
unset USER
unset PROJECT
yankcrime marked this conversation as resolved.
Show resolved Hide resolved
unset PROJECT_ID
```
7 changes: 5 additions & 2 deletions pkg/providers/openstack/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,10 @@ func (p *PasswordProvider) Client(ctx context.Context) (*gophercloud.ProviderCli
IdentityEndpoint: p.endpoint,
UserID: p.userID,
Password: p.password,
TenantID: p.projectID,
AllowReauth: true,
Scope: &gophercloud.AuthScope{
yankcrime marked this conversation as resolved.
Show resolved Hide resolved
ProjectID: p.projectID,
},
}

return authenticatedClient(ctx, options)
Expand All @@ -120,8 +122,9 @@ type DomainScopedPasswordProvider struct {

// Ensure the interface is implemented.
var _ CredentialProvider = &DomainScopedPasswordProvider{}
var _ CredentialProvider = &PasswordProvider{}

// NewDomainScopedPasswordProvider creates a client that comsumes passwords
// NewDomainScopedPasswordProvider creates a client that consumes passwords
// for authentication.
func NewDomainScopedPasswordProvider(endpoint, userID, password, domainID string) *DomainScopedPasswordProvider {
return &DomainScopedPasswordProvider{
Expand Down
22 changes: 15 additions & 7 deletions pkg/providers/openstack/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ type Provider struct {
// secret is the current region secret.
secret *corev1.Secret

domainID string
userID string
password string
domainID string
projectID string
userID string
password string

// DO NOT USE DIRECTLY, CALL AN ACCESSOR.
_identity *IdentityClient
Expand Down Expand Up @@ -141,11 +142,16 @@ func (p *Provider) serviceClientRefresh(ctx context.Context) error {
return fmt.Errorf("%w: password", ErrKeyUndefined)
}

// Pass in an empty string to use the default project.
providerClient := NewDomainScopedPasswordProvider(region.Spec.Openstack.Endpoint, string(userID), string(password), string(domainID))
projectID, ok := secret.Data["project-id"]
if !ok {
return fmt.Errorf("%w: project-id", ErrKeyUndefined)
}

// Create the clients.
identity, err := NewIdentityClient(ctx, providerClient)
// 'Regular' client calls to APIs for Nova, Glance etc. must to be project-scoped
providerClient := NewPasswordProvider(region.Spec.Openstack.Endpoint, string(userID), string(password), string(projectID))
yankcrime marked this conversation as resolved.
Show resolved Hide resolved

// Identity client is scoped to a domain to use the manager role
identity, err := NewIdentityClient(ctx, NewDomainScopedPasswordProvider(region.Spec.Openstack.Endpoint, string(userID), string(password), string(domainID)))
if err != nil {
return err
}
Expand All @@ -170,6 +176,8 @@ func (p *Provider) serviceClientRefresh(ctx context.Context) error {
p.secret = secret

p.domainID = string(domainID)
p.projectID = string(projectID)

p.userID = string(userID)
p.password = string(password)

Expand Down
Loading