Overview
When you send an HTTP request to OCP, OCP needs to calculate a signature for your request and generate an authentication string. This process helps identify the requestor, protect the data security during transmission, and prevent replay attacks.
The system uses the access key of OCP for the signature calculation. The access key consists of the access key ID (AK) and the secret access key (SK).
Sign the request
Obtain an Access Key ID and Access Key Secret.
Log in to the OCP console and click Personal Settings in the upper-right corner. On the page that appears, select Create AccessKey Pair.
Create the string to be signed.
Create the string to be signed based on the request information and additional information, such as the algorithm and request time.
Calculate the signature.
Use the Access Key Secret as the encryption key to perform an HMAC operation on the string to be signed to generate the signature.
Add the signature to the request.
After the signature is calculated, add it to the HTTP header of the request.
Procedure
Step 1: Obtain the AK/SK
Log in to the OCP console.
Hover the cursor over the username in the upper-right corner and click Personal Settings from the menu that appears.
Go to the Personal Settings page. In the AccessKey section, click Create AccessKey Pair. For more information, see Configure personal information.
Please keep your AK and SK safe after they are created.
Step 2: Create the string to be signed
Concatenate the following strings separated by line breaks to create the string to be signed, which has the following format:
<HttpMethod>
<Md5Payload>
<ContentType>
<RequestTime>
<Host>
<OCP-headers>
<PathAndQueryParams>
The formula for calculating the string to be signed is as follows:
toSignString = HttpMethod + "\n" + Md5Payload + "\n" + ContentType + "\n" + RequestDate + "\n" + Host + "\n" + OCP-Headers + "\n" + PathAndQueryParams
As shown in the formula, the string to be signed consists of seven parts: HttpMethod, Md5Payload, ContentType, RequestDateTime, Host, OCP-Headers, and PathAndQueryParams. The specific meanings and calculation methods of these parts are as follows:
HttpMethod: the HTTP request method, such as GET, POST, PUT, and DELETE.
Md5Payload: the MD5 value of the request payload. If the request does not contain a valid payload, pass an empty string (""). If the request contains a payload, pass the MD5 value of the payload (
md5(<payload>)).ContentType: the request type, which is the content-type value in the request headers. For most requests in OCP, the value is
application/json.RequestDateTime: the request time. This information is read from the Date or x-ocp-date value in the request headers. The time format follows the RFC-1123 format, such as
Fri, 12 Apr 2024 07:15:32 GMT.Host: the domain name of the request. If the value is
IP+Port, pass IP:Port, such asxxx.xxx.xxx.xxx:8080.OCP-Headers: the request headers that start with
x-ocp-. The signature algorithm includes these request headers in the calculation. Multiple headers are sorted in alphabetical order and separated by line breaks.HeaderName1+":"+<value>+"\n" HeaderName2+":"+<value>+"\n" ... HeaderNameN+":"+<value>+"\n"PathAndQueryParams: the combination of the request path and request parameters. Multiple parameters are sorted in alphabetical order, such as
?a=1&b=2. The specific concatenation algorithm is as follows.path?urlEncode(<param1>)+"="+URLEncode(<value>)+"&"+urlEncode(<param2>)+"="+URLEncode(<value>)+...+"&"+urlEncode(<paramN>)+"="+URLEncode(<value>)
Example
A cURL request example is as follows:
curl --user <username>:<password> -H "x-ocp-origin:for-test" -H "Content-Type:application/json" -X GET "http://xxx.xxx.xxx.xxx:8080/api/v2/monitor/top?metrics=host_disk_total&labels=svr_ip:xxx.xxx.xxx.xxx&groupBy=app,svr_ip,device,mount_point&startTime=2024-04-15T14:29:55+08:00&endTime=2024-04-15T14:30:55+08:00&maxPoints=360"
- HttpMethod:
GET - Md5PayLoad: empty
- ContentType:
application/json - RequestDateTime: assumed to be
Mon, 15 Apr 2024 09:25:02 GMT - Host:
xxx.xxx.xxx.xxx:8080 - OCP-headers:
x-ocp-origin:for-test - PathAndQueryParams:
Path:
/api/v2/monitor/topQueryParams:
metrics=host_disk_total&labels=svr_ip:xxx.xxx.xxx.xxx&groupBy=app,svr_ip,device,mount_point&startTime=2024-04-15T14:29:55+08:00&endTime=2024-04-15T14:30:55+08:00&maxPoints=360The concatenated string is:
/api/v2/monitor/top?endTime=2024-04-15T14%3A30%3A55%2B08%3A00&groupBy=app%2Csvr_ip%2Cdevice%2Cmount_point&labels=svr_ip%3Axxx.xxx.xxx.xxx&maxPoints=360&metrics=host_disk_total&startTime=2024-04-15T14%3A29%3A55%2B08%3A00
The final string to be signed is:
GET
application/json
Mon, 15 Apr 2024 09:25:02 GMT
xxx.xxx.xxx.xxx:8080
x-ocp-origin:for-test
/api/v2/monitor/top?endTime=2024-04-15T14%3A30%3A55%2B08%3A00&groupBy=app%2Csvr_ip%2Cdevice%2Cmount_point&labels=svr_ip%3Axxx.xxx.xxx.xxx&maxPoints=360&metrics=host_disk_total&startTime=2024-04-15T14%3A29%3A55%2B08%3A00
Step 3: Calculate the signature
The signature algorithm is as follows:
BASE64(HMAC-SHA1(<AccessKey Secret>, string-to-sign.getBytes("UTF-8")))
Step 4: Add the signature to the request
When you call an OCP API, you must follow these rules:
When you send an HTTP request to OCP, make sure that the HTTP header contains the Authorization and Date fields, so that OCP can authenticate the request.
Authorization: the authorization information of the request, including the prefix, signature algorithm, AK, and signature. The format is
OCP-ACCESS-KEY-HMACSHA1 {AK}:{Signature}.Prefix: currently fixed to
OCP-ACCESS-KEY-.Signature algorithm: currently only supports
HMACSHA1. Note thatHMACSHA1must be in uppercase.AK: the Access Key ID. Note that there is a space between the signature algorithm and the AK.
Signature: the calculated signature. For more information about the calculation rules, see Step 3: Calculate the signature.
Date: the time when the request is initiated. It is used to verify whether the current request is within a reasonable time range (the deviation from the time when the server receives the request must not exceed 15 minutes). The time format is RFC-1123, such as
Tue, 17 Jan 2023 03:36:01 GMT.
After receiving your request, the OCP server parses the signature information and request information and verifies them. If the verification is successful, it proceeds to process the request. Otherwise, it returns an error.
Notice
The AK and SK belong to a user and have the same permissions as the user.
Here is an example:
curl -H "Authorization: OCP-ACCESS-KEY-HMACSHA1 <AK>:<signed-string>" -H "Date:Fri, 12 Apr 2024 07:15:32 GMT" -H "x-ocp-origin:for-test" -H "Content-Type:application/json" -X GET "xxx.xxx.xxx.xxx:8080/api/v2/monitor/top?metrics=host_disk_total&labels=svr_ip:xxx.xxx.xxx.xxx&groupBy=app,svr_ip,device,mount_point&startTime=2024-04-15T14:29:55+08:00&endTime=2024-04-15T14:30:55+08:00&maxPoints=360"
Complete example
The previous section describes how to concatenate the string to be encrypted, the signature algorithm, and the signature message body. This section provides an example of the AK/SK signature generation process.
Assume that the current AK/SK is as follows:
AccessKey ID:gDCcIqbkJJINjXBn
AccessKey Secret:d75332c5eed8d440a84a35ac6248d397
The example request reuses the content from the previous example:
curl --user <username>:<password> -H "x-ocp-origin:for-test" -H "Content-Type:application/json" -X GET "http://xxx.xxx.xxx.xxx:8080/api/v2/monitor/top?metrics=host_disk_total&labels=svr_ip:xxx.xxx.xxx.xxx&groupBy=app,svr_ip,device,mount_point&startTime=2024-04-15T14:29:55+08:00&endTime=2024-04-15T14:30:55+08:00&maxPoints=360"
Based on the description in the previous section, the string to be encrypted is:
GET
application/json
Mon, 15 Apr 2024 09:25:02 GMT
xxx.xxx.xxx.xxx:8080
x-ocp-origin:for-test
/api/v2/monitor/top?endTime=2024-04-15T14%3A30%3A55%2B08%3A00&groupBy=app%2Csvr_ip%2Cdevice%2Cmount_point&labels=svr_ip%3Axxx.xxx.xxx.xxx&maxPoints=360&metrics=host_disk_total&startTime=2024-04-15T14%3A29%3A55%2B08%3A00
The signature calculation logic is as follows:
BASE64(HMAC-SHA1("d75332c5eed8d440a84a35ac6248d397", "GET
application/json
Mon, 15 Apr 2024 09:25:02 GMT
xxx.xxx.xxx.xxx:8080
x-ocp-origin:for-test
/api/v2/monitor/top?endTime=2024-04-15T14%3A30%3A55%2B08%3A00&groupBy=app%2Csvr_ip%2Cdevice%2Cmount_point&labels=svr_ip%3Axxx.xxx.xxx.xxx&maxPoints=360&metrics=host_disk_total&startTime=2024-04-15T14%3A29%3A55%2B08%3A00".getBytes("UTF-8")))
The calculated signature is:
Signature:
To11kg1EsB/dPWyDnnpuUzIUoQk=API request using AK/SK:
curl -H "Authorization: OCP-ACCESS-KEY-HMACSHA1 gDCcIqbkJJINjXBn:To11kg1EsB/dPWyDnnpuUzIUoQk=" -H "Date:Mon, 15 Apr 2024 07:49:49 GMT" -H "x-ocp-origin:for-test" -H "Content-Type:application/json" -X GET "xxx.xxx.xxx.xxx:8080/api/v2/monitor/top?metrics=host_disk_total&labels=svr_ip:xxx.xxx.xxx.xxx&groupBy=app,svr_ip,device,mount_point&startTime=2024-04-15T14:29:55+08:00&endTime=2024-04-15T14:30:55+08:00&maxPoints=360"
Java signature sample code
package com.oceanbase.ocp.demo;
import org.junit.Assert;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.math.BigInteger;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringJoiner;
import java.util.stream.Collectors;
public class OcpSignDemo {
public static void main(String[] args) throws Exception {
Map<String, String> params = new HashMap<>();
params.put("startTime", "2024-04-15T14:29:55+08:00");
params.put("endTime", "2024-04-15T14:30:55+08:00");
params.put("maxPoints", "360");
params.put("metrics", "host_disk_total");
params.put("labels", "svr_ip:127.0.0.1");
params.put("groupBy", "app,svr_ip,device,mount_point");
String path = "/api/v2/monitor/top";
Map<String, String> headers = new HashMap<>();
headers.put("x-ocp-origin", "for-test");
headers.put("Content-Type", "application/json");
String accessKeyId = "gDCcIqbkJJINjXBn";
String accessKeySecret = "d75332c5eed8d440a84a35ac6248d397";
String rfcDate = "Mon, 15 Apr 2024 09:25:02 GMT";
String sign = getSign("127.0.0.1:8080", path, "GET", headers, params, null, accessKeySecret, rfcDate);
Assert.assertEquals("To11kg1EsB/dPWyDnnpuUzIUoQk=", sign);
// headers need add to request.
System.out.println("Request Headers:");
System.out.println("Authorization: OCP-ACCESS-KEY-HMACSHA1" + accessKeyId + ":" + sign);
System.out.println("Date:" + rfcDate);
}
public static String getSign(String host, String path, String method, Map<String, String> headers, Map<String,
String> params, byte[] body, String accessKeySecret) throws Exception {
return getSign(host, path, method, headers, params, body, accessKeySecret, getRfcDate());
}
public static String getSign(String host, String path, String method, Map<String, String> headers, Map<String,
String> params, byte[] body, String accessKeySecret, String rfcDate) throws Exception {
StringJoiner strToSign = new StringJoiner("\n");
// Append method.
strToSign.add(method);
// Append payload.
if (body == null) {
strToSign.add("");
} else {
strToSign.add(md5(body));
}
// Append Content-type.
if (headers == null || !headers.containsKey("Content-Type")) {
strToSign.add("");
} else {
strToSign.add(headers.get("Content-Type"));
}
// Append request-time.
strToSign.add(rfcDate);
// Append host.
strToSign.add(host);
// Append ocp-headers.
if (headers == null || headers.keySet().stream().noneMatch(s -> s.startsWith("x-ocp-"))) {
strToSign.add("");
} else {
String ocpHeadersStr = headers.entrySet().stream()
.filter(e -> e.getKey().toLowerCase().startsWith("x-ocp"))
.sorted(Map.Entry.comparingByKey())
.map(e -> String.format("%s:%s", e.getKey(), String.join(",", e.getValue())))
.collect(Collectors.joining("\n"));
strToSign.add(ocpHeadersStr);
}
// Append path and params
if (params == null || params.isEmpty()) {
strToSign.add(path);
} else {
List<Map.Entry<String, String>> toSort = new ArrayList<>(params.entrySet());
toSort.sort(Map.Entry.comparingByKey());
StringJoiner joiner = new StringJoiner("&");
for (Map.Entry<String, String> e : toSort) {
String enc = StandardCharsets.UTF_8.displayName();
String format = String.format("%s=%s", URLEncoder.encode(e.getKey(), enc),
URLEncoder.encode(e.getValue(), enc));
joiner.add(format);
}
String paramsStr = joiner.toString();
strToSign.add(path + "?" + paramsStr);
}
return Base64.getEncoder().encodeToString(hmacSha1(accessKeySecret,
strToSign.toString().getBytes(StandardCharsets.UTF_8)));
}
private static String getRfcDate() {
return Instant.now().atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME);
}
private static String md5(byte[] bs) throws Exception {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(bs);
String hex = new BigInteger(1, digest.digest()).toString(16).toUpperCase();
return new String(new char[32 - hex.length()]).replace("\0", "0") + hex;
}
private static byte[] hmacSha1(String accessKeySecret, byte[] content) {
try {
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(accessKeySecret.getBytes(StandardCharsets.UTF_8), "HmacSHA1"));
return mac.doFinal(content);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("Not supported signature method HmacSHA1", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("Failed to calculate the signature", e);
}
}
}