PX-Security using the OpenStorage Python SDK

Portworx introduced its open-source SDK called OpenStorage with its 1.6 release to enable programmatic access to the Portworx data layer. For the purpose of this blog, we will skip over the long version of “What is OpenStorage” and point you to where you can read more about it here. If you want the short version, OpenStorage facilitates the programmatic provisioning of cloud native volumes for Kubernetes and gives DevOps teams the ability to control and automate their cloud native storage volumes via APIs in a flexible manner.
 
In this post, we’ll focus on the newest features of Portworx Enterprise 2.1 which includes PX-Security. PX-Security expands the Portworx security footprint beyond volume encryption to include TLS as well as container-granular role-based authentication, authorization, and ownership which we’ll explore using the SDK. If you would like to know more about PX-Security outside of the context of the Python SDK, check out our blog covering it in depth.
 

Security Workflow

The first thing to do as a developer is head over to https://libopenstorage.github.io/ and take a look around. There are resources on how to use the SDK with both Go and Python and some information worth reading.
 
To set up your python environment, check out the OpenStorage Python Tutorial.
 
Below is a small Python program called create_vol.py which is available here to try and create a volume and see what happens.
 

[ryan@server ~]$ python examples/kubernetes/create_vol.py
Connecting to 70.0.0.129:19020
Failed: code=StatusCode.UNAUTHENTICATED msg=Request unauthenticated with bearer

 
Yes, that is expected. Now that we are using a cluster with PX-Security enabled and is hooked up to an OIDC provider the cluster will look for a valid token in order to make authorized requests. This token is a JWT Token and contains information about the user and the roles or groups they belong to in order to authorize them to interact with the platform.
 
We need to authenticate with an issuer which will give us a token that contains the claims we have access to in order for us to be authorized to create a volume. For this example and demo, I’ll be using the Okta identity provider to act as our OIDC provider and token issuer.
 
okta
 
Within Okta, we can represent Users and Roles that allow different interactions with Portworx. We can create a system.admin role or group and tie that to a given user, which will then have access to All APIs and all resources. We can also create other types of roles recognized by Portworx such as system.user and system.view which give limited access to resources in the cluster. Below are examples of two users, one with the admin role and another with the view role.
 
Ryan Wallner px-security
 
So, let’s modify the Python script and to use the new release of the SDK which lets us pass in a token to a request. We’ll also update our script so it can let us authenticate and retrieve a token to use in our requests. Below are some snippets from the modified script, take particular note to the following sections:
 
We need to use an endpoint to fetch our token, in this case, my endpoint looks like this, however other OIDC providers will offer similar /token endpoints for you to use if you are not using Okta.
 

OKTA_ENDPOINT = os.getenv('PWX_OKTA_ENDPOINT',
 'https://dev-399155.okta.com/oauth2/default/v1/token')

 
Next, to request a token, we’ll use the password grant type to retrieve our token. You will see the grant type as well as other relevant information needed in the token such as scope which makes sure we have items such as email and name in our token. The script will prompt the user for secret information such as the user’s password and client secret from the issuer.
 

PARAMS = {
   'client_id': client_id,
   'client_secret': client_secret,
   'grant_type': "password",
   'redirect_uri': "localhost:8080",
   'username': username,
   'password': password,
   'scope': "openid profile email"
} 

 
We can then submit the request and retrieve the token.
 

# sending get request and saving the response as response object 
r = requests.post(url = OKTA_ENDPOINT, data = PARAMS) 
  
# extracting data in json format 
data = r.json()
openid_token = data[u'id_token']

 
Then, we can include the bearer token in the metadata that we pass to the Portworx API.
 

md = []
md.append(("authorization", "bearer "+openid_token))
try:
    # Cluster connection
    clusters = api_pb2_grpc.OpenStorageClusterStub(channel)
    ic_resp = clusters.InspectCurrent(api_pb2.SdkClusterInspectCurrentRequest(), metadata=md)
    print('Connected to {0} with status {1}'.format(
        ic_resp.cluster.id,
        api_pb2.Status.Name(ic_resp.cluster.status)
    ))

 
Now we can use API endpoints such as SdkVolumeCreateRequest while being authenticated and with a role that enables us to create a volume.
 
Let’s put it all together and try to create a volume with both users. First, we’ll create a volume with the system.admin user.
 

./auth_create_vol.py
Using OIDC Endpoint: https://dev-399155.okta.com/oauth2/default/v1/token
Is this OIDC Endpoint OK? (y/n): y
What is the client id for the OIDC? 0oadayf3z49qyURWh356
What is the Client Secret for the OIDC?
What is your Username? ryan.wallner@portworx.com
What is your Password:
Retrieved your token: 
Connecting to 70.0.0.129:19200
Connected to a05fffe3-4eef-4591-bec8-2e2ed9921237 with status STATUS_OK
What is the name of your volume? myvol2
What is the ha_level of your volume? 2
Volume id is 456668846699764309

 
Success! Okay, now let’s try again but use the user with the system.view Role that was defined in Okta.
 

 ./auth_create_vol.py
Using OIDC Endpoint: https://dev-399155.okta.com/oauth2/default/v1/token
Is this OIDC Endpoint OK? (y/n): y
What is the client id for the OIDC? 0oadayf3z49qyURWh356
What is the Client Secret for the OIDC?
What is your Username? developer@portworx.com
What is your Password:
Retrieved your token: 
Connecting to 70.0.0.129:19200
Connected to a05fffe3-4eef-4591-bec8-2e2ed9921237 with status STATUS_OK
What is the name of your volume? myvol3
What is the ha_level of your volume? 1
Failed: code=StatusCode.PERMISSION_DENIED msg=Access to /openstorage.api.OpenStorageVolume/Create denied: rpc error: code = PermissionDenied desc = Access denied to roles: [system.view]

 
Great! Exactly what we expected, we cannot create volumes if we don’t have access to a Role that lets us.
 
Check out the below demo for an overview of the above. The demo will also show how to add other users as collaborators to volumes and how access can be added and removed on the fly.
 

Avatar

Portworx | Technical Advocate

Share Share on Facebook Tweet about this on Twitter Share on LinkedIn



Back to Blog