10

Hardening access to Kyma APIs with a self-made JWT token.

 3 years ago
source link: https://blogs.sap.com/2021/11/25/hardening-access-to-kyma-apis-with-a-self-made-jwt-token./
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
November 25, 2021 4 minute read

Hardening access to Kyma APIs with a self-made JWT token.

Mission statement.

Nihil novi sub sole, the WWW is not a very safe place. Thus this is paramount to protect the public API endpoints from unauthorised access.

In Hardening access to Kyma APIs with JWT tokens I demonstrated how to protect public  Kyma APIs with JWT tokens, using SAP BTP XSUAA as OIDC provider.

But sometimes, if on-premise or edge application needed to call into a protected Kyma API, a self-generated JWT might be a simpler approach….

Let’s see how this can be done…

Putting it all together

The client application (the caller) will need to fetch a digitally signed JWT token and pass it in the Authorization header of the API call (the callee).

Subsequently, the digitally signed JWT token will be validated by the callee using the issuer and the jwks_uri URLs as defined in the API Rule  access strategy.

Step1. Generate a digitally signed JWT token

The following code snippet demonstrates how to generate a digitally signed JWT token using the standard jsonwebtoken library, namely: https://www.npmjs.com/package/jsonwebtoken.

const  cert = '-----BEGIN CERTIFICATE-----\nMIIFGzCCAwMCBGBb1dwwDQYJKoZIhvcNAQELBQAwUjELMAkGA1UEBhMCVVMxDDAK\nBgNVBAoMA1NBUDEVMBMGA1UECwwMY
.........truncated.........................
URdaHVX6B270SUDiP6TDPApn9E+IaISzPRpk\nXT6c0QNVYg37DBU/qhSN\n-----END CERTIFICATE-----\n'; 

const key = '-----BEGIN PRIVATE KEY-----\nMIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDFz/eQv30tj5oC\nLjT1Im7OtVAVo6mB/wQbEpbOh3LSI
.........truncated.........................
LSxBTGdiZznqgLKnImxU1WDSA2xlKJy7J\nAwx8lLYgANSJ7qkKPgPR/t5ZHrx/plY=\n-----END PRIVATE KEY-----\n';
const jwt = require('jsonwebtoken');
const { randomUUID } = require('crypto'); // Added in: node v14.17.0

function generateAccessToken(token_endpoint, jwks_uri) {

  return jwt.sign( {iss: token_endpoint, jti: randomUUID() }, key, { algorithm: 'RS256', keyid: 'QtV2kV6VfnvJpRHCHFVXjDPFKydXWVHjuz4XIE0xxxx', header: {jku: jwks_uri}, expiresIn: '3600sec' } );
}

Step2. Create an API Rule protected with a self-made JWT token.

Let’s create a new API Rule with a JWT access strategy for our  backend Kyma function…

  • The issuer and jwks_uri urls must match the iss and jku attributes of the self-generated JWT token as depicted in the below table

Issuer URL

  • http://poster-app.default.svc.cluster.local

JWKS URI URL

  • http://poster-app.default.svc.cluster.local/jwks_uri
  • In order to achieve a better resilience both URLs point to local kyma cluster resources

Picture16-3.png

  • When trying to call this API Rule directly we are getting the Unauthorized (401) error. This is expected and by design.
{"error":{"code":401,"status":"Unauthorized","request":"<request>","message":"The request could not be authorized"}}

Step3. Bootstrap the API

Let’s have a look at the following code snippet.

That’s the “bootstrapping” code needed to call an API endpoint secured with a digitally signed JWT token.

const axios = require('axios')
const jwt = require('jsonwebtoken');

//The url is the JWT-protected API Rule enpoint
//The logonToken is the self-generated bearer JWT token

  try {
      logonToken = generateAccessToken(
              'http://poster-app.default.svc.cluster.local', 
              'http://poster-app.default.svc.cluster.local/jwks_uri'
              );
      
      let decoded = await jwt.decode(logonToken, { complete: true });
      console.log('decoded logonToken:', decoded);
 
      let configGet = {
        method: 'get',
        url: url,
        headers: { 
          "Authorization": 'Bearer ' + logonToken,
        }
      };  
      const response = await axios(configGet);
      console.log(response.status);
      return response.data;
  }
  catch(error) {
      console.log(error);
      return error;
  };  

Step4. Test the API

Calling the API Rule from the CURL command:

curl -ik https://<hostname>.<domain>/here_location?location=Massy -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vb2VtLWF6dXJlLmF1dGhlbnRpY2F0aW9uLmFwMjEuaGFuYS5vbmRlbWFuZrsKGSwfr6KoREMSrJ7wxp80NgoFFqD8CS2lK4OSoXmIQqWJsRZzCHfa-rKA'

HTTP/2 200 

{
  "items": [
    {
      "title": "Massy, Île-de-France, France",
      "id": "here:cm:namedplace:20004823",
      "resultType": "locality",
      "localityType": "city",
      "address": {
        "label": "Massy, Île-de-France, France",
        "countryCode": "FRA",
        "countryName": "France",
        "stateCode": "IDF",
        "state": "Île-de-France",
        "county": "Essonne",
        "city": "Massy",
        "postalCode": "91300"
      },
   ]
}

Last but not least…

  • The bootstrapping nodejs code snippet above, with axios as the http client: https://www.npmjs.com/package/axios, is for illustration purposes only.
  • The bootstrapping sequence is fairly simple but may need to be adapted to the caller’s native environment itself: GO, ABAP, JAVA, C++, etc..
  • The JWT generation should be to be done at the edge. As it can be implemented outside of the on premise system that is calling out the Kyma API, there is not much need to adapt it to the caller’s native environment.

Conclusion.

It is not that uncommon to self-generate a digitally signed JWT. Different identity platforms literally explain how to do it. There are other libraries that can help you do it as well, for instance https://github.com/jwtk/njwt…

I hope the approach I demonstrated in this blog post will help you consider SKR – SAP managed k8s cluster a a service as a perfect platform to host extensions to a variety of business applications. Worth mentioning that SKR do not charge for function/API calls – it is all included in the price of the cluster….


Appendix

JWKS keys

The JWKS array must contains the public x509 certificate from your certificate keypair as to be able to decode the JWT signed digitally with the private key of the keypair. It must be available on Kyma side but for the sake of enhanced security and resilience I have made the JWKS available only locally on the Kyma cluster where the callee api is hosted.

{
  "keys": [
    {
      "kid": "QtV2kV6VfnvJpRHCHFVXjDPFKydXWVHjuz4XIE0xxx",
      "kty": "RSA",
      "alg": "RS256",
      "use": "sig",
      "n": "xc_3kL99LY-aAi409SJuzrVQFaOpgf8EGxKWzody0iPIf39NH8CzCf7kN52B4sKrBJU7ygNIdAebZu_cP2dRQcf7646q2yr0BBVUP8x92FRL
...................................................(truncated)..................................
RsaXQ6T6RNJTmk1FpFpgI2wO_gtaRSdVxA1cMpzvfsfiDhK-gEDDhoYQCYWzsL-X_0",
      "e": "AQAB",
      "x5c": [
        "MIIFGzCCAwMCBGBb1dwwDQYJKoZIhvcNAQELBQAwUjELMAkGA1UEBhMCVVMxDDAKBgNVBAoMA1NBUDEVMBMGA1UECwwMYXRl
...................................................(truncated)..................................
9934p3PLnZAJOhLJO0X6cHCFbMC+6GxXTdisQVivIOKUURdaHVX6B270SUDiP6TDPApn9E+IaISzPRpkXT6c0QNVYg37DBU/qhSN"
      ],
      "x5t": "DQMOdr9ITZxhd6NYI7l8y6LIdTw",
      "x5t#S256": "NINub7bHeCqT1mEJiQcUdbJtJgSmTeQhw48DGAJ1r1w"
    }
  ]
}

SAP_Best_scrn_R_blk_pos-1.png


Recommend

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK