Skip to content

Commit

Permalink
Scope various client requests to a project (#44)
Browse files Browse the repository at this point in the history
The default API policies for OpenStack were amended almost unversally
(across projects) with the 2023.1 release to limit information returned
from client requests that are not scoped to a project, see the Glance
release notes for example:

https://docs.openstack.org/releasenotes/glance/2023.1.html#upgrade-notes
  • Loading branch information
yankcrime authored Aug 1, 2024
1 parent 9fab8f8 commit b4b82f1
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 20 deletions.
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
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
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{
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))

// 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

0 comments on commit b4b82f1

Please sign in to comment.