Kong has lots of plugins available to do varies kind of tasks, Security is a big topic among the plugins , no one wants to expose API to internet without any protection. we use HMAC Plugin to do API level authentication. with HMAC Plugin the password never go through the wire, and we could also enable the body verification as an option. this is a good option for us to open API to the public internet

kong

enable the hmac auth plugin

apiVersion: configuration.konghq.com/v1
kind: KongPlugin
metadata:
  name: hmac-auth
plugin: hmac-auth

this will enable the plugin in k8s env and we could enable the plugin by putting an annotation value in the ingress yaml file

annotations:
    plugins.konghq.com: hmac-auth

config username and password

hmac plugin need a Consumer and 1 or more credentials, the credentials should be attached to the Consumer

apiVersion: configuration.konghq.com/v1
kind: KongConsumer
metadata:
  name: consumer-hmac
username: api-user

---

apiVersion: configuration.konghq.com/v1
kind: KongCredential
metadata:
  name: credential-hmac
consumerRef: consumer-hmac
type: hmac-auth
config:
  username: whoami1
  secret: xxxxxxxxxxxxx

---

apiVersion: configuration.konghq.com/v1
kind: KongCredential
metadata:
  name: credential-hmac
consumerRef: consumer-hmac
type: hmac-auth
config:
  username: whoami2
  secret: xxxxxxxxxxxxx

when we deploy these consumere and credentails, the ingress which enalbe the hmac-auth plugin is protected.

generate client code to call the server

to call the hmac-auth protected api, we must generate the client signature and pass it in http header, to generate hmac hash, kong support sha1, sha256,sha384 and sha512, sha1 is not recommended as it's not secure, I will use sha256 in this article. like code below we could use password xxxxxxxxxxxxx to generate hash data for any given data

private String hmacHash(String key, String data)
      throws Exception {
    String algorithm = "HmacSHA256";
    SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(), algorithm);
    Mac mac = Mac.getInstance(algorithm);
    mac.init(signingKey);
    return Base64.getEncoder().encodeToString(mac.doFinal(data.getBytes()));
  }

with Kong hmac we must pass Date in GMT format through http header and pass Authorization for the calulcated hash, Authorization is formated as hmac username="whoami1", algorithm="hmac-sha256", headers="Date", signature="hmacHash(dateString, xxxxxxxxxxxxx)"

  • hmac is telling the server we are using hmac plugin to to authentication
  • username="whoami1" is the user name we created in the KongCredential, then server could located this user's password on server side
  • algorithm="hmac-sha256" will tell server we are using sha265 algorithm to calculate hash
  • headers="Date" will tell server we use Date as the input source to calculate hash
  • signature="hmacHash(dateString, xxxxxxxxxxxxx)" is the signature with result of hmacHash calculated on client side based on "Date" header to send back to server. the server will sueee the same password for whoami1 to caluclate hash for "Date" header, if the calculated hash match given signature, auth pass, otherwise auth failed.
private Map<String, String> headers(String username, String password) throws Exception {
    Map<String, String> headers = Maps.newLinkedHashMap();
    DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss");
    df.setTimeZone(TimeZone.getTimeZone("GMT"));
    String date = df.format(new Date()) + " GMT";
    headers.put("Date", date);
    String rawSign = "Date: " + date;

    String authHeader = String.format(
        "hmac username=\"%s\", algorithm=\"hmac-sha256\", headers=\"Date\", signature=\"%s\"",
        username, hmacHash(password, rawSign));

    headers.put("Authorization", authHeader);

    return headers;
  }

test auth in java code

code below will function well with only Google Guava as a dependency, we could use http client to write more elegant code to handle http request and response.

  @Test
  public void testRequest() throws Exception {

    Map<String, String> headers = headers("whoami1", "xxxxxxxxxxxxx");
    URL url = new URL("http://api.vipmind.me/hello/v1");
    HttpURLConnection con = (HttpURLConnection) url.openConnection();
    con.setRequestMethod("POST");
    con.setDoOutput(true);
    //set the header
    headers.entrySet().forEach(entry -> con.setRequestProperty(entry.getKey(), entry.getValue()));
    //set the timeout
    con.setConnectTimeout(5000);
    con.setReadTimeout(5000);
    //send out the call the server
    try (OutputStream os = con.getOutputStream()) {
      byte[] input = Files.toByteArray(new File(Resources.getResource("data.json").getFile()));
      os.write(input, 0, input.length);
    }

    if (con.getResponseCode() == 200) {
      //receive response
      try (BufferedReader br = new BufferedReader(
          new InputStreamReader(con.getInputStream(), "utf-8"))) {
        StringBuilder response = new StringBuilder();
        String responseLine = null;
        while ((responseLine = br.readLine()) != null) {
          response.append(responseLine.trim());
        }
        System.out.println(response.toString());
      }
    } else {
      try (BufferedReader br = new BufferedReader(
          new InputStreamReader(con.getErrorStream(), "utf-8"))) {
        StringBuilder response = new StringBuilder();
        String responseLine = null;
        while ((responseLine = br.readLine()) != null) {
          response.append(responseLine.trim());
        }
        System.out.println(response.toString());
      }
    }

    con.disconnect();
  }

test auth in curl

we could also construct curl command to call the api

  @Test
  public void testCurl() throws Exception {
    StringBuilder call = new StringBuilder();
    call.append("curl -XPOST");
    Map<String, String> headers = headers("whoami1", "xxxxxxxxxxxxx");
    headers.entrySet().forEach(entry -> call
        .append(" -H '")
        .append(entry.getKey())
        .append(": ")
        .append(entry.getValue())
        .append("'"));
    call.append(" -d \"@data.json\"");
    call.append(" http://api.vipmind.me/hello/v1");

    System.out.println(call.toString());
  }