Registration workflows
Basic authentication flow
A plain text username and password. Underlying library base64 encodes "user:pass"
.
.kurl.register (`basic; domain; tenant; `user`pass!(user; pass))
Example:
.kurl.register (`basic; "http://localhost"; .z.u; `user`pass!(.z.u;"password"))
.kurl.sync ("http://localhost/v1/service?test=1";`GET;``tenant!(::;.z.u))
OAuth2 authentication flow
When building a new application to use single sign on (SSO), you may create a client ID and client secret in an identity provider’s online portal.
You will use the information you specify in the portal, along with generated IDs returned by the portal, to create an OAuth2 client specification that you may package with your application.
When you start up your application, you may start a login flow using Kurl’s helper .kurl.oauth2.startLoginFlow
. Kurl will automatically start a worker process called ‘the sidecar‘
on your machine using the port specified in your OAuth2 client specification.
When asked for a redirection URI, include a redirection URI http://localhost:port
, where the port is any number you want to use on your local machine.
The sidecar will open a browser window at the authentication endpoint listed in your client specification.
You will then type in your user account information and sign into the authentication endpoint. After successfully logging in, by using HTTP redirects, your Web browser will return a token response back to the Kurl library. Your personal credentials are never passed through Kurl, or returned to us by the browser. The browser gives us only tokens that can be used on your behalf.
An OAuth2 login flow:
The entire OAuth2 Login flow process can be thought of as requesting and getting an access token.
Kurl’s startLoginFlow
can be visualized as a simple callback that triggers after getting an access token.
Several minutes prior to the expiration of the access token, Kurl makes a request to the token URI endpoint using the refresh token to acquire a new access token.
OAuth2 client specification
OAuth2 clients must provide the following information. Key names are chosen to match Google’s downloadable JSON; if using something else, make the keys match.
client_id 10h OAuth2 Client ID
client_secret 10h OAuth2 Client Secret
auth_uri 10h SSO providers Authentication endpoint (GET)
token_uri 10h SSO providers Token endpoint (POST)
redirect_uris 0h list of permitted URLs to redirect authentication response to
When providing a specification to Kurl, pass in a dictionary with the above information.
Optionally, you may set the ENV KX_OAUTH2_CLIENT_JSON
to a path to a JSON file that has the above specification, and Kurl will read that file in, if the dictionary is left blank.
An example JSON client specification is below:
Note the extra "web"
key. The title here is not important to Kurl, however for ease of use with Google for SSO we will
look under whatever the "first"
key is, because Google inserts a key.
{
"web":{
"client_id" : "redacted",
"auth_uri" : "https://accounts.google.com/o/oauth2/auth",
"token_uri" : "https://oauth2.googleapis.com/token",
"client_secret" : "redacted",
"redirect_uris" : ["http://localhost:1234"]
}
}
When using Azure or other services, the auth_uri
and token_uri
may contain identifiers within the URLs themselves
OAuth2 Login API
Calling the following API will start the login flow:
.kurl.oauth2.startLoginFlow[domain;client;overrides;callback]
value | type | desc | example |
---|---|---|---|
domain | 10h | wildcard for when to use stored information | "*.googleapis.com" |
spec | 99h (or env var) | OAuth2 client specification | see OAuth2 Client Specification |
overrides | 99h | optional values for authentication | `scope`access_type`prompt!("openid email";"offline";"consent") |
callback | 100h/104h | callback allows you to make a following request | {[tenant; auth_response] } |
The domain is a wildcard that matches requests endpoints the login will be valid for. It is not the domain of the OAuth2 endpoint. The OAuth2 endpoint should be in the client specification.
Note
In the event of a successful login, the auth_response will be a dictionary, else an error message.
OAuth2 standard access token response
OAuth2 servers should return a standard response.
Kurl supports only token_type Bearer
, and expects the content to be JSON.
Kurl looks for the following JSON values.
access_token
expires_in optional, if present must be in seconds
refresh_token optional
Handling non-standard access token responses
If a server or utility does not return the following keys in JSON, (spelled exactly as follows), you must provide the key names to .kurl.register
so it knows
how to map the response.
access_token
expires_in optional, if present must be in seconds
refresh_token optional
For example, suppose we have a utility that prints an access token in camel case. This is a practical example Kurl handles when using the access tokens for an existing Azure CLI login.
$ az account get-access-token
{
"accessToken": "token value here",
"expiresOn": "2021-01-10 17:48:18.766268",
"subscription": "b574a496-8d3e-4430-b935-19c579ebcc9a",
"tenant": "95451270-5f0c-4cee-9d65-1473e3f04320",
"tokenType": "Bearer"
}
Kurl offers the following optional keys for registering non-standard token responses. These keys should be inserted into the authInfo
of a registration input.
All values are symbols or strings, excluding expires_onformat_len
, which should be a positive number.
access_token_key title for access_token
refresh_token_key title for refresh_token
expires_in_key title for expires_in
expires_on_key title for expires_on
expires_on_format date format string acceptable by strptime()
expires_on_len maximum length of expires_on to scan
These values tell Kurl the JSON key names to use when refreshing OAuth2 tokens.
In our above example, Kurl would need the following ‘corrections’.
rawresponse:`accessToken`expiresOn!
("token value here"; "2021-01-10 17:48:18.766268")
kys:`access_token_key`expires_on_key`expires_on_format`expires_on_format_len
corrections:kys!("accessToken"; "expiresOn"; "%Y-%m-%d %H:%M:%S"; 19 )
authInfo: rawresponse , corrections
Using the above corrections allows Kurl to maintain tokens that need to be refreshed now that it knows the titles to use.
OAuth2 authentication flow with QPacker
When using QPacker with the OAuth2 login flow, ensure the ports of your redirect_uri
are accessible from outside the container running Kurl.
For example, if your client specification file contains the following
{
"web":{
"client_id" : "redacted",
"auth_uri" : "https://accounts.google.com/o/oauth2/auth",
"token_uri" : "https://oauth2.googleapis.com/token",
"client_secret" : "redacted",
"redirect_uris" : ["http://localhost:1234"]
}
}
then the sidecar will listen on port 1234 and this port must be exposed by the container running kurl
.
Example of this using Docker:
# Build and tag a container image containing kurl
qp build myapp
qp tag myapp 0.0.1
# Run the image including qp environment variables and expose port 1234
docker run --env-file=qpbuild/.env -it --rm -p 1234:1234 myapp:0.0.1
You may also need to mount the folder containing your client specification file into the running container so that kurl
can access it.
Remote OAuth2 authentication flow
If you are running Kurl on a machine remotely and want to use OAuth2 then you need to specify the redirect URI as part of the overrides
dictionary parameter.
For example, if you are running kurl on a VM called test.codekx.com
and want to run the sidecar on port 1234 this could be specified as
.kurl.oauth2.startLoginFlow[
"*.googleapis.com";
`:client.json;
`access_type`prompt`redirect_uri!
("offline";"consent";"http://test.codekx.com:1234");
{[tenant;auth] show tenant} ]
This value must be an authorized redirect URI. These are generally specified in the developer’s console of the identity provider.
For Development
This workflow will expose your machines IP to the web, it is not recommended for production use.
For production systems, other authentication methods that rely on system indenties, not user indenties should be used.
AWS authentication
AWS authentication requires an access key ID, secret access key, and a token, if one was provided.
These values can be sourced from various AWS services, environment variables, or disk.
Capitalized names must exactly match AWS response case and spelling. Lowercase values are used by Kurl and named for consistency between authentication types.
Using temporary security credentials with the AWS CLI
AccessKeyId
-
Amazon Key ID, required
-
e.g.
getenv `AWS_ACCESS_KEY_ID
Expiration
-
String of form
"%Y%m%dT%H%M%SZ"
, optional -
e.g.
"2020-12-09T23:59:00Z"
SecretAccessKey
-
Key Secret (required)
-
e.g.
getenv `AWS_SECRET_ACCESS_KEY
Token
-
Token for Refresh endpoint, required
-
e.g.
getenv `AWS_SESSION_TOKEN
initial_sts_response
-
Initial XML response returned by STS; required only if using Amazon Secure Token Service (STS)
-
e.g. Example STS response
url
-
URL for library to ask to generate next access key; required only if
Expiration
is specified -
e.g.
"http://169.254.169.254/latest/meta-data/iam/security-credentials/rolename"
web_token_file
-
Filename where Amazon updates web token, used by Kubernetes pod integration; required only if using Amazon Secure Token Service (STS)
-
e.g.
getenv `AWS_WEB_IDENTITY_TOKEN_FILE
AWS instance metadata service
If you are logged into an EC2 instance, and have assigned an IAM role to an EC2 instance when creating the instance, Kurl will automatically discover the temporary access credentials.
For listing a EC2 instances metadata, refer to retrieving instance metadata
Kurl stores the following information, and you may consider Kurl’s relationship with the VM instance read-only.
The VM is responsible for rotating access tokens, and Kurl is simply pointing at the value, and reloading the information prior to expiry.
{
"Code" : "Success",
"LastUpdated" : "2020-12-09T17:30:48Z",
"Type" : "AWS-HMAC",
"AccessKeyId" : "redacted",
"SecretAccessKey" : "redacted",
"Token" : "redacted",
"Expiration" : "2020-12-09T23:59:00Z"
}
AWS user environment
AWS environment variables are one of the authentication inputs automatically registered by the library.
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_SESSION_TOKEN
AWS STS WebTokens
If you are logged into a Kubernetes pod, the library will automatically use AWS_ROLE_ARN
and AWS_WEB_IDENTITY_TOKEN_FILE
if they are available.
Introducing fine-grained IAM roles for service accounts
Google Cloud Platform authentication
GCP Instance Metadata Service
If you are logged into an Google Cloud Platform VM instance, and have assigned a service account to the VM instance, Kurl will automatically discover the temporary access credentials.
Listing a VM instances metadata for manual inspection
Kurl stores the following information, and you may consider its access to the VM instance as read-only.
The VM is responsible for rotating access tokens, and Kurl is simply pointing at the value, and reloading the information prior to expiry.
access_token
-
OAuth2 Access Token, see example below
expires_in
-
Expiry in number of seconds, e.g. 2944
headers
-
"Metadata-Flavor: Google"
header, e.g. -
enlist[`$"Metadata-Flavor"]!enlist "Google"
method
-
GET, needed to tell Kurl this is not a typical POST endpoint, e.g.
`GET
token_uri
-
Instance metadata URL e.g.
-
"http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"
GCP user environment
If you have installed the Google Cloud CLI Gcloud, the Kurl library will automatically use your Cloud CLI login.
Use gcloud auth login
to sign in to Google.
If you are signed in, Kurl will use the following system command to get your login token.
gcloud auth print-access-token
GCP single signon
Using Google for single signon can be done by having an administrator create a OAuth2 Client ID in Google Cloud Platform.
When created, download the client JSON, and use OAuth2.
GCP Identity-Aware Proxy
For access to a REST server protected by Google Cloud Platforms Identity-Aware Proxy (IAP) refer to:
Enabling IAP
Programmatic Authentication to IAP
GCP Identity Aware Proxy example
IAP uses the JSON Web Token (JWT) extension for OAuth2: RFC 7519.
Using IAP makes initial requests to sign in as above, followed by a request from the signed-in user to access the application.
The initial request to login has no relationship to IAP
You are logging in first, accepting an agreement, and then requesting IAP access afterwards, which generates a JWT token.
Kurl provides a built-in convenience function for making this follow-up request to get the JWT token for an IAP service .kurl.oauth2.grantAudience
.
audience: "IAP ID"
baseurl: "your https IAP URL"
.kurl.oauth2.startLoginFlow[
"https://openidconnect.googleapis.com";
spec;
`scope`access_type`prompt!("openid email";"offline";"consent");
.kurl.oauth2.grantAudience[audience; baseurl; client; callback;] ]
Server domain
Unlike first-party apps, the domain is openidconnect.googleapis.com
instead of the server you ultimately want to access.
The server you want to access is specified to the grantAudience
callback as the baseurl
parameter.
Azure authentication
Azure user environment
If you have installed the Azure CLI az
, the Kurl library will automatically use your CLI login.
Use az login
to sign in to Azure.
If you are logged into the Azure CLI, Kurl will grant itself several access tokens.
At the time of writing this covers all Azure services that support Active Directory
az account get-access-token --resource https://management.azure.com
az account get-access-token --resource https://vault.azure.net
az account get-access-token --resource https://datalake.azure.net
az account get-access-token --resource https://database.windows.net
az account get-access-token --resource https://eventhubs.azure.net
az account get-access-token --resource https://servicebus.azure.net
az account get-access-token --resource https://*.asazure.windows.net
az account get-access-token --resource https://storage.azure.com
If you want to modify the list Kurl uses set KX_KURL_AZURE_AD_RESOURCES
to a space-separated list of URLs.
export KX_KURL_AZURE_AD_RESOURCES="https://management.azure.com https://vault.azure.com"
Azure Instance Metadata Service
If you are logged into an Azure VM instance, and have assigned a managed identity to the VM instance, Kurl will automatically discover the temporary access credentials.
For setting up a VM instance to use a managed identity see the following:
How to use managed identities for Azure resources on an Azure VM to acquire an access token
Managed identity
Azure Active Directory
Authentication to Azure to access most services can be done by setting up an Azure Active Directory tenant to use OAuth2, and registering with Kurl.
OAuth2 Login API
OAuth2 client specification
Some services may support either SharedKey or OAuth2, such as Azure Storage.
How to optionally use AD
Azure services that support Azure AD authentication
Azure API management
For access to a REST server protected by Azure API management:
Protect a web API backend
Azure API Management Example
When using Azure as the service provider and the identity provider, login is the same as the first-party case.
If you have enabled the Subscription ID option for the Azure API Management service, you must specify the ID when making sync or async requests.
You can find the querystring name and header name for where to specify the ID under the API Settings > Subscription.
In the console these are displayed with the following defaults
Header name Ocp-Apim-Subscription-Key
Query parameter name subscription-key
// Example joining on query string
.kurl.sync (url,"?subscription-key=xyz";`GET;::)
Subscription ID
The subscription ID under API > Subscriptions is not the same as the Subscription ID for your entire organization or company.
It is a subscription ID unique to the client who subscribed to the application.
Azure shared key
A select few Azure services support Header Signing V2 and require a shared key for access, e.g. Azure Storage and Azure Monitor (Logging).
Shared keys allow an application to access a resource and its contained entities. It does not allow general access to the Azure platform.
Providing a shared key is done by calling .kurl.register
.
If you wish to register against a single storage account, a simpler ENV var approach can be used.
When registering, two lists of headers need specifying:
sign_headers
-
custom
x-ms-
headers Azure wants listed, typically calledCanonocalizedHeaders
variable in examples sign_values
-
standard HTTP header names Azure wants the values signed for, typically part of the
StringToSign
in examples
The headers and values differ for each service:
Azure Monitor Authentication
Azure Storage
Why does Kurl not detect and supply these for you? It is because some headers are optionally omitted from authentication, and we cannot determine what headers Microsoft intends to be required and what are not.
value | desc | example |
---|---|---|
account_name | alias for ID, the Storage account ID (only applies to storage) |
copy from Azure Storage account |
id | Logging workspace ID, Storage account ID, etc. | see below |
shared_key | Azure shared key | copy from Azure (called Primary or Secondary key) |
sign_values | string list of standard headers to include in authentication | ("Content-Length";"Content-Type") |
sign_headers | string list of CanonocalizedHeaders |
enlist "x-ms-date" |
workspace_id | alias for ID, the Logging workspace ID (only applies to logging) |
copy from Azure Logging workspace |
To use an Azure service that supports header signing that is not Logging or Storage, use id
Authorization: SharedKey <id will be here in Azure documentation>:<Signature>
Azure shared key single storage account
You may set environment variables AZURE_STORAGE_ACCOUNT
and AZURE_STORAGE_SHARED_KEY
to have kurl perform registration,
on your behalf, instead of explicitly calling .kurl.register. Setting these variables has the same effect as calling register like so:
account:getenv `AZURE_STORAGE_ACCOUNT;
sk:getenv `AZURE_STORAGE_SHARED_KEY;
sign_headers:("x-ms-date"; "x-ms-version"; "x-ms-blob-type"; "x-ms-copy-source");
sign_values:("Content-Encoding";"Content-Language";"Content-Length";"Content-MD5"; "Content-Type");
sign_values,:("Date"; "If-Modified-Since"; "If-Match"; "If-None-Match"; "If-Unmodified-Since"; "Range");
credInfo:`account_name`shared_key`sign_headers`sign_values!(
account;
sk;
sign_headers;
sign_values);
uri:"https://",account,".blob.core.windows.net";
.kurl.register (`azure; uri; ""; credInfo);
If you wish to communicate with multiple storage accounts, then you should see the above section on shared keys.
If you intend to use any x-ms- headers other than x-ms-date, x-ms-version, x-ms-blob-type, x-ms-copy-source, you must register manually, and update sign_headers accordingly.