Friday, October 26, 2018

Securing Oracle Service Bus REST services with OAuth2 (without using additional products)

OAuth2 is a popular authentication framework. As a service provider it is thus common to provide support for OAuth2. How can you do this on a plain WebLogic Server / Service Bus without having to install additional products (and possibly have to pay for licenses)? If you just want to implement and test the code, see this installation manual. If you want to know more about the implementation and choices made, read on!

OAuth2 client credentials flow

OAuth2 supports different flows. One of the easiest to use is the client credentials flow. It is recommended to use this flow when the party requiring access can securely store credentials. This is usually the case when there is server to server communication (or SaaS to SaaS).

The OAuth2 client credentials flow consists of an interaction pattern between 3 actors which all have their own roll in the flow.
  • The client. This can be anything which supports the OAuth2 standard. For testing I've used Postman
  • The OAuth2 authorization server. In this example I've created a custom JAX-RS service which generates and returns JWT tokens based on the authenticated user.
  • A protected service. In this example I'll use an Oracle Service Bus REST service. The protection consists of validating the token (authentication using standard OWSM policies) and providing role based access (authorization).
When using OAuth2, the authorization server returns a JSON message containing (among other things) a JWT (JSON Web Token).

In our case the client authenticates using basic authentication to a JAX-RS servlet. This uses the HTTP header Authorization which contains 'Basic' followed by Base64 encoded username:password. Of course Base64 encoded strings can be decoded easily (e.g. by using sites like these) so never use this over plain HTTP!

When this token is obtained, it can be used in the Authorization HTTP header using the Bearer keyword. A service which needs to be protected can be configured with the following standard OWSM policies for authentication: oracle/http_jwt_token_service_policy and oracle/http_jwt_token_over_ssl_service_policy and a custom policy for role based access / authorization.

JWT

JSON Web Tokens (JWT) can look something like:

eyJraWQiOiJvYXV0aDJrZXlwYWlyIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJ3ZWJsb2dpYyIsImlzcyI6Ind3dy5vcmFjbGUuY29tIiwiZXhwIjoxNTQwNDY2NDI4LCJpYXQiOjE1NDA0NjU4Mjh9.ZE8wMnFyjHcmFpdswgx3H8azVCPtHkrRjqhiKt-qZaV1Y5YlN9jAOshUnPIQ76L8K4SAduhJg7MyLQsAipzCFeT_Omxnxu0lgbD2UYtz-TUIt23bjcsJLub5pNrLXJWL3k7tSdkcVxlyHuRPYCvoLhLZzCksqnRdD6Zf9VjxGLFPktknXwpn7_aOAdzXEatj-Gd9lm321R2BdFL7ii9sXh9A1KL8cblLbhLlrXGwTF_ifTxuHSBz1B_p6xng6kmOfIwDIAJQ9t6KESQm8dQQeilcny1uRmhg4o85uc4gGzhH435q1DRuHQm22wN39FHbNT4WP3EuoZ49PpsTeQzSKA

This is not very helpful at first sight. When we look a little bit closer, we notice it consists of 3 parts separated by a '.' character. These are the header, body and signature of the token. The first 2 parts can be Base64 decoded.

Header

The header typically consists of 2 parts (see here for an overview of fields and their meaning). The type of token and the hashing algorithm. In this case the header is

{"kid":"oauth2keypair","alg":"RS256"}

kid refers to the key id. In this case it provides a hint to the resource server on which key alias to use in its key store to validate the signature.

Body

The JWT body contains so-called claims. In this case the body is

{"sub":"weblogic","iss":"www.oracle.com","exp":1540466428,"iat":1540465828}

The subject is the subject for which the token was issued. www.oracle.com is the issuer of the token. iat indicates an epoch at which the token was issued and exp indicates until when the token is valid. Tokens are valid only for a limited duration. www.oracle.com is an issuer which is accepted by default so no additional configuration was required.

Signature

The signature contains an encrypted hash of the header/body of the token. If those are altered, the signature validation will fail. To encrypt the signature, a key-pair is used. Tokens are signed using a public/private key pair.

Challenges

Implementing the OAuth2 client credentials flow using only a WebLogic server and OWSM can be challenging. Why?
  • Authentication server. Bare WebLogic + Service Bus do not contain an authentication server which can provide JWT tokens.
  • Resource Server. Authentication of tokens. The predefined OWSM policies which provide authentication based on JWT tokens (oracle/http_jwt_token_service_policy and oracle/http_jwt_token_over_ssl_service_policy) are picky to what tokens they accept.
  • Resource Server. Authorization of tokens. OWSM provides a predefined policy to do role based access to resources: oracle/binding_permission_authorization_policy. This policy works for SOAP and REST composites and Service Bus SOAP services, but not for Service Bus REST services.
How did I fix this?
  • Create a simple authentication server to provide tokens which conform to what the predefined OWSM policies expect. By increasing the OWSM logging and checking for errors when sending in tokens, it becomes clear which fields are expected.
  • Create a custom OWSM policy to provide role based access to Service Bus REST resources
Custom components

Authentication server

The authentication server has several tasks:
  • authenticate the user (client credentials) 
    • using the WebLogic security realm
  • validate the client credentials request
    • using Apache HTTP components
  • obtain a public and private key for signing 
    • from the OPSS KeyStoreService (KSS)
  • generate a token and sign it 
Authentication

User authentication on WebLogic Server of servlets consists of 2 configuration files.

A web.xml. This file indicates
  • which resources are protected
  • how they are protected (authentication method, TLS or not)
  • who can access the resources (security role)

The weblogic.xml indicates how the security roles map to WebLogic Server roles. In this case any user in the WebLogic security realm group tokenusers (which can be in an external authentication provider such as for example an AD or other LDAP) can access the token service to obtain tokens.


Validate the credentials request

From Postman you can do a request to the token service to obtain a token. This can also be used if the response of the token service conforms to the OAuth2 standard.

By default certificates are checked. With self-signed certificates / development environments, those checks (such as host name verification) might fail. You can disable the certificate checks in the Postman settings screen.


Also Postman has a console available which allows you to inspect requests and responses in more detail. The request looked like


Thus this is what needed to be validated; an HTTP POST request with a body containing application/x-www-form-urlencoded grant_type=client_credentials. I've used the Apache HTTP components org.apache.http.client.utils.URLEncodedUtils class for this.

After deployment I of course needed to test the token service. Postman worked great for this but I could also have used Curl commands like:

curl -u tokenuser:Welcome01 -X POST -d "grant_type=client_credentials" http://localhost:7101/oauth2/resources/tokenservice

Accessing the OPSS keystore

Oracle WebLogic Server provides Oracle Platform Security Services.


OPSS provides secure storage of credentials and keys. A policy store can be configured to allow secure access to these resources. This policy store can be file based, LDAP based and database based. You can look at your jps-config.xml file to see which is in use in your case;


You can also look this up from the EM


In this case the file based policy store system-jazn-data.xml is used. Presence of the file on the filesystem does not mean it is actually used! If there are multiple policy stores defined, for example a file based and an LDAP based, the last one appears to be used.

The policy store can be edited from the EM


You can create a new permission:

Codebase: file:${domain.home}/servers/${weblogic.Name}/tmp/_WL_user/oauth2/-
Permission class: oracle.security.jps.service.keystore.KeyStoreAccessPermission
Resource name: stripeName=owsm,keystoreName=keystore,alias=*
Actions: read

The codebase indicates the location of the deployment of the authentication server (Java WAR) on WebLogic Server.

Or when file-based, you can edit the (usually system-jazn-data.xml) file directly

In this case add:

<grant>
<grantee>
<codesource>
<url>file:${domain.home}/servers/${weblogic.Name}/tmp/_WL_user/oauth2/-</url>
</codesource>
</grantee>
<permissions>
<permission>
<class>oracle.security.jps.service.keystore.KeyStoreAccessPermission</class>
<name>stripeName=owsm,keystoreName=keystore,alias=*</name>
<actions>*</actions>
</permission>
</permissions>
</grant>

At the location shown below


Now if you create a stripe owsm with a policy based keystore called keystore, the authentication server is allowed to access it!



The name of the stripe and name of the keystore are the default names which are used by the predefined OWSM policies. Thus when using these, you do not need to change any additional configuration (WSM domain config, policy config). OWSM only supports policy based KSS keystores. When using JKS keystores, you need to define credentials in the credential store framework and update policy configuration to point to the credential store entries for the keystore password, key alias and key password. The provided code created for accessing the keystore / keypair is currently KSS based. Inside the keystore you can import or generate a keypair. The current Java code of the authentication server expects a keypair oauth2keypair to be present in the keystore.


Accessing the keystore and key from Java

I defined a property file with some parameters. The file contained (among some other things relevant for token generation):

keystorestripe=owsm
keystorename=keystore
keyalias=oauth2keypair

Accessing the keystore can be done as is shown below.

            AccessController.doPrivileged(new PrivilegedAction<String>() {
                public String run() {
                    try {
                        JpsContext ctx = JpsContextFactory.getContextFactory().getContext();
                        KeyStoreService kss = ctx.getServiceInstance(KeyStoreService.class);
                        ks = kss.getKeyStore(prop.getProperty("keystorestripe"), prop.getProperty("keystorename"), null);
                    } catch (Exception e) {
                        return "error";
                    }
                    return "done";
                }
            });

When you have the keystore, accessing keys is easy

            PasswordProtection pp = new PasswordProtection(prop.getProperty("keypassword").toCharArray());
            KeyStore.PrivateKeyEntry pkEntry = (KeyStore.PrivateKeyEntry) ks.getEntry(prop.getProperty("keyalias"), pp);

(my key didn't have a password but this still worked)

Generating the JWT token

After obtaining the keypair at the keyalias, the JWT token libraries required instances of RSAPrivateKey and RSAPublicKey. That could be done as is shown below

            RSAPrivateKey myPrivateKey = (RSAPrivateKey) pkEntry.getPrivateKey();
            RSAPublicKey myPublicKey = (RSAPublicKey) pkEntry.getCertificate().getPublicKey();

In order to sign the token, an RSAKey instance was required. I could create this from the public and private key using a RSAKey.Builder method.

            RSAKey rsaJWK = new RSAKey.Builder(myPublicKey).privateKey(myPrivateKey).keyID(prop.getProperty("keyalias")).build();

Using the RSAKey, I could create a Signer

JWSSigner signer = new RSASSASigner(rsaJWK);

Preparations were done! Now only the header and body of the token. These were quite easy with the provided builder.

Claims:

JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.subject(user)
.issuer(prop.getProperty("tokenissuer"))
.expirationTime(expires)
.issueTime(new Date(new Date().getTime()))
.build();

Generate and sign the token:

SignedJWT signedJWT = new SignedJWT(new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(rsaJWK.getKeyID()).build(), claimsSet);
signedJWT.sign(signer);
String token = signedJWT.serialize();


Returning an OAuth2 JSON message could be done with

String output = String.format("{ \"access_token\" : \"%s\",\n" + "  \"scope\"        : \"read write\",\n" +  "  \"token_type\"   : \"Bearer\",\n" + "  \"expires_in\"   : %s\n}", token,expirytime);

Role based authorization policy

The predefined OWSM policies oracle/http_jwt_token_service_policy and oracle/http_jwt_token_over_ssl_service_policy create a SecurityContext which is available from the $inbound/ctx:security/ctx:transportClient inside Service Bus. Thus you do not need a custom identity asserter for this!

However, the policy does not allow you to configure role based access and the predefined policy oracle/binding_permission_authorization_policy does not work for Service Bus REST services. Thus we need a custom policy in order to achieve this. Luckily this policy can use the previously set SecurityContext to obtain principles to validate.

Challenges

Provide the correct capabilities to the policy definition was a challenge. The policy should work for Service Bus REST services. Predefined policies provide examples, however they could not be exported from the WSM Policies screen. I did 'Create like' a predefined policy which provided the correct capabilities and then copied those capability definitions to my custom policy definition file. Good to know: some capabilities required the text 'rest' to be part of the policy name.

Also I encountered a bug in 12.2.1.2 which is fixed with the following patch: Patch 24669800: Unable to configure Custom OWSM policy for OSB REST Services. In 12.2.1.3 there were no issues.

An OWSM policy consists of two deployments

A JAR file

  • This JAR contains the Java code of the policy. The Java code uses the parameters defined in the file below.
  • A policy-config.xml file. This file indicates which class is implementing the policy. Important part of this file is the reference to restUserAssertion. This maps to an entry in the file below
A policy description ZIP file
  • This contains a policy description file. 
The description ZIP file contains a single XML file which answers questions like;
  • Which parameters can be set for the policy? 
  • Of which type are the parameters? 
  • What are the default values of the parameters?
  • Is it an authentication or authorization policy?
  • Which bindings are supported by the policy?
The policy description file contains an element which maps to the entry in the policy-config.xml file. Also the ZIP file has a structure which is in line with the name and Id of the policy. It is like;


Thus the name of the policy is CUSTOM/rest_user_assertion_policy
This name is also part of the contents of the rest_user_assertion_policy file. You can also see there is again a reference to the implementation class and the restUserAssertion element which is in the policy-config.xml file is also there. The capabilities of the policy are mentioned in the restUserAssertion attributes.


Finally

As mentioned before, the installation manual and code can be found here. Of course this solution does not provide all the capabilities of a product like API Platform Cloud Service, OAM, OES. Usually you don't need all those capabilities and complexity and just a simple token service /policy is enough. In such cases you can consider this alternative. Of course since it is hosted on WebLogic / Service Bus, it needs some extra protection when exposed to the internet such as a firewall, IP whitelisting, SSL offloading, etc.

3 comments:

  1. Hi Maarten, excellent work, I trying implement oauth in my composite, I followed step by step your manual, but I get 401 error. I would like know if you could help me please?

    ReplyDelete
  2. Hi Maarten, I need to implement Oauth JWT authorization mechanism in my project, I followed step by step your documentation but i get 401 error, the log show me the next errors



    <Failure in Oracle WSM Agent processRequest, category=security, function=agent.function.service, application=Service Bus Kernel, composite=null, modelObj=null, policy=oracle/http_jwt_token_service_policy, policyVersion=null, assertionName={http://schemas.oracle.com/ws/2006/01/securitypolicy}http-jwt-security.
    oracle.wsm.common.sdk.WSMException: GenericFault : generic error

    ReplyDelete