From b5fcf807d6a16e0acb50846382200ee4df2a02c8 Mon Sep 17 00:00:00 2001 From: Zhenlan Wang Date: Mon, 9 Nov 2020 20:14:52 -0800 Subject: [PATCH 1/3] Add reference to REST API official documentation --- README.md | 21 +- docs/Development/fiddler.md | 60 --- docs/Development/postman.md | 32 -- docs/REST/authentication/aad.md | 64 ---- docs/REST/authentication/hmac.md | 587 ------------------------------ docs/REST/authentication/index.md | 11 - docs/REST/authorization/aad.md | 43 --- docs/REST/authorization/hmac.md | 13 - docs/REST/authorization/index.md | 11 - docs/REST/consistency.md | 48 --- docs/REST/headers.md | 37 -- docs/REST/keys.md | 170 --------- docs/REST/kv.md | 382 ------------------- docs/REST/labels.md | 170 --------- docs/REST/locks.md | 102 ------ docs/REST/revisions.md | 204 ----------- docs/REST/throttling.md | 43 --- docs/REST/versioning.md | 71 ---- 18 files changed, 1 insertion(+), 2068 deletions(-) delete mode 100644 docs/Development/fiddler.md delete mode 100644 docs/Development/postman.md delete mode 100644 docs/REST/authentication/aad.md delete mode 100644 docs/REST/authentication/hmac.md delete mode 100644 docs/REST/authentication/index.md delete mode 100644 docs/REST/authorization/aad.md delete mode 100644 docs/REST/authorization/hmac.md delete mode 100644 docs/REST/authorization/index.md delete mode 100644 docs/REST/consistency.md delete mode 100644 docs/REST/headers.md delete mode 100644 docs/REST/keys.md delete mode 100644 docs/REST/kv.md delete mode 100644 docs/REST/labels.md delete mode 100644 docs/REST/locks.md delete mode 100644 docs/REST/revisions.md delete mode 100644 docs/REST/throttling.md delete mode 100644 docs/REST/versioning.md diff --git a/README.md b/README.md index 843bd7bd..f526f248 100644 --- a/README.md +++ b/README.md @@ -14,26 +14,7 @@ Subscribe to the following repo to be notified of announcements and updates abou ## REST API Reference -The following reference pages are available to describe the Azure App Configuration API surface in detail. - -**Resources** - * [Keys](./docs/REST/keys.md) - * [Key-Values](./docs/REST/kv.md) - * [Labels](./docs/REST/labels.md) - * [Locks](./docs/REST/locks.md) - * [Revisions](./docs/REST/revisions.md) - -**Protocol** - * [Authentication](./docs/REST/authentication/index.md) - * [Authorization](./docs/REST/authorization/index.md) - * [Consistency Model](./docs/REST/consistency.md) - * [Common Headers](./docs/REST/headers.md) - * [Throttling](./docs/REST/throttling.md) - * [Versioning](./docs/REST/versioning.md) - -**Development** - * [Fiddler](./docs/Development/fiddler.md) - * [Postman](./docs/Development/postman.md) +Access full functionalities of App Configuration via its [REST APIs]((https://docs.microsoft.com/azure/azure-app-configuration/rest-api)) from any programming languages including those that don't have SDKs or development tools you often use. ## Client Libraries diff --git a/docs/Development/fiddler.md b/docs/Development/fiddler.md deleted file mode 100644 index 412b159e..00000000 --- a/docs/Development/fiddler.md +++ /dev/null @@ -1,60 +0,0 @@ -# Fiddler Configuration - REST API Reference -# -To test the REST API using [Fiddler](https://www.telerik.com/fiddler), requests need to include the headers required for [authentication](../REST/authentication/hmac.md). Here's how to configure Fiddler for testing the REST API, generating the authentication headers automatically: - -1. Ensure that TLS 1.2 is an allowed protocol - - Open the HTTPS options (Tools->Options->HTTPS). Ensure that `Decrypt HTTPS traffic` is checked. Then in the list of protocols, add `tls1.2` if not present. - -2. Open "Fiddler Script Editor" or press Ctrl-R within Fiddler - -3. Add the following code inside the Handlers class before the OnBeforeRequest function - - ```js - static function SignRequest(oSession: Session, credential: String, secret: String) { - var utcNow = DateTimeOffset.UtcNow.ToString("r", System.Globalization.DateTimeFormatInfo.InvariantInfo); - var contentHash = ComputeSHA256Hash(oSession.RequestBody); - var stringToSign = oSession.RequestMethod.ToUpperInvariant() + "\n" + oSession.PathAndQuery + "\n" + utcNow +";" + oSession.hostname + ";" + contentHash; - var signature = ComputeHMACHash(secret, stringToSign); - - oSession.oRequest.headers["x-ms-date"] = utcNow; - oSession.oRequest.headers["x-ms-content-sha256"] = contentHash; - oSession.oRequest.headers["Authorization"] = "HMAC-SHA256 Credential=" + credential + "&SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=" + signature; - } - - static function ComputeSHA256Hash(content: Byte[]) { - var sha256 = System.Security.Cryptography.SHA256.Create(); - try { - return Convert.ToBase64String(sha256.ComputeHash(content)); - } - finally { - sha256.Dispose(); - } - } - - static function ComputeHMACHash(secret: String, content: String) { - var hmac = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(secret)); - try { - return Convert.ToBase64String(hmac.ComputeHash(System.Text.Encoding.ASCII.GetBytes(content))); - } - finally { - hmac.Dispose(); - } - } - ``` - -4. Add the following code at the end of the OnBeforeRequest function and update the access key as indicated by the TODO comment - - ```js - if (oSession.isFlagSet(SessionFlags.RequestGeneratedByFiddler) && - oSession.hostname.EndsWith(".azconfig.io", StringComparison.OrdinalIgnoreCase)) { - - // TODO: Replace the following placeholders with your access key - var credential = ""; // Id - var secret = ""; // Value - - SignRequest(oSession, credential, secret); - } - ``` - -5. Use [Fiddler's Composer](https://docs.telerik.com/fiddler/Generate-Traffic/Tasks/CreateNewRequest) to generate and send a request diff --git a/docs/Development/postman.md b/docs/Development/postman.md deleted file mode 100644 index ea8aa931..00000000 --- a/docs/Development/postman.md +++ /dev/null @@ -1,32 +0,0 @@ -# Postman Configuration - REST API Reference -# -To test the REST API using [Postman](https://www.getpostman.com/), requests need to include the headers required for [authentication](../REST/authentication/hmac.md). Here's how to configure Postman for testing the REST API, generating the authentication headers automatically: - -1. Create a new [request](https://learning.getpostman.com/docs/postman/sending_api_requests/requests/) - -2. Add the `signRequest` function from the [JavaScript authentication sample](../REST/authentication/hmac.md#JavaScript) to the [pre-request script](https://learning.getpostman.com/docs/postman/scripts/pre_request_scripts/) for the request - -3. Add the following code to the end of the pre-request script and update the access key as indicated by the TODO comment - - ```js - // TODO: Replace the following placeholders with your access key - var credential = ""; // Id - var secret = ""; // Value - - var isBodyEmpty = pm.request.body === null || pm.request.body === undefined || pm.request.body.isEmpty(); - - var headers = signRequest( - pm.request.url.getHost(), - pm.request.method, - pm.request.url.getPathWithQuery(), - isBodyEmpty ? undefined : pm.request.body.toString(), - credential, - secret); - - // Add headers to the request - headers.forEach(header => { - pm.request.headers.upsert({key: header.name, value: header.value}); - }) - ``` - -4. Send the request diff --git a/docs/REST/authentication/aad.md b/docs/REST/authentication/aad.md deleted file mode 100644 index a087b804..00000000 --- a/docs/REST/authentication/aad.md +++ /dev/null @@ -1,64 +0,0 @@ -# Azure Active Directory Authentication - REST API Reference - -HTTP requests may be authenticated using the **Bearer** authentication scheme with a token acquired from Azure Active Directory (AAD). These requests must be transmitted over TLS. - -*Prerequisites*: -- The principal that will be used to request an AAD token must be assigned to one of the applicable [App Configuration roles](../authorization/aad.md) - -Provide each request with all HTTP headers required for Authentication. The minimum required are: - -| Request Header | Description | -| --------------- | ------------ | -| **Authorization** | Authentication information required by **Bearer** scheme. Format and details are explained below. | - - -**Example:** -``` -Host: {myconfig}.azconfig.io -Authorization: Bearer {{AadToken}} -``` - - -## Azure Active Directory Token Acquisition - -Before acquiring an AAD token one must identify what user they want to authenticate as, what audience they are requesting the token for, and what AAD endpoint (authority) they should use. - -**Audience** - -The AAD token must be requested with a proper audience. For Azure App Configuration one of the following audiences should be specified when requesting a token. The audience may also be referred to as the "resource" that the token is being requested for. - - - {configurationStoreName}.azconfig.io - - *.azconfig.io - -**Important:** When the audience requested is {configurationStoreName}.azconfig.io, it must exactly match the "Host" request header (case sensitive) used to send the request. - -**AAD Authority** - -The AAD authority is the endpoint that is used for acquiring an AAD token. It is in the form of `https://login.microsoftonline.com/{tenantId}`. The `{tenantId}` segement refers to the Azure Active Directory tenant id to which the user/application who is trying to authenticate belongs. - -**Authentication Libraries** - -Azure provides a set of libraries called Azure Active Directory Authentication Libraries (ADAL) to simplify the process of acquiring an AAD token. These libraries are built for multiple languages. Documentation can be found [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-authentication-libraries). - -## **Errors** - -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256, Bearer -``` -**Reason:** Authorization request header with Bearer scheme is not provided. -**Solution:** Provide valid ```Authorization``` HTTP request header - -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256, Bearer error="invalid_token", error_description="Authorization token failed validation" -``` -**Reason:** The AAD token is not valid. -**Solution:** Acquire an AAD token from the AAD Authority and ensure the proper audience is used. - -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256, Bearer error="invalid_token", error_description="The access token is from the wrong issuer. It must match the AD tenant associated with the subscription, to which the configuration store belongs. If you just transferred your subscription and see this error message, please try back later." -``` -**Reason:** The AAD token is not valid. -**Solution:** Acquire an AAD token from the AAD Authority and ensure the AAD Tenant is the one associated with the subscription, to which the configuration store belongs. This error may appear if the principal belongs to more than one AAD tenant. diff --git a/docs/REST/authentication/hmac.md b/docs/REST/authentication/hmac.md deleted file mode 100644 index 59a28f9e..00000000 --- a/docs/REST/authentication/hmac.md +++ /dev/null @@ -1,587 +0,0 @@ -# HMAC Authentication - REST API Reference -# -HTTP requests may be authenticated using the **HMAC-SHA256** authentication scheme. These requests must be transmitted over TLS. - -*Prerequisites*: -- **Credential** - \ -- **Secret** - base64 decoded Access Key Value. ``base64_decode()`` - -The values for credential (also called 'id') and secret (also called 'value') must be obtained from the Azure App Configuration instance. This can be done using the [Azure Portal](https://portal.azure.com) or the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest). - -Provide each request with all HTTP headers required for Authentication. The minimum required are: - -| Request Header | Description | -| --------------- | ------------ | -| **Host** | Internet host and port number. See section [3.2.2](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.2.2) | -| **Date** | Date and Time at which the request was originated. It can not be more than 15 min off from current GMT. The value is an HTTP-date, as described in section [3.3.1](https://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1) -| **x-ms-date** | Same as ```Date``` above. It can be used instead when the agent can't directly access ```Date``` request header or a proxy modifies it. If ```x-ms-date``` and ```Date``` are both provided, ```x-ms-date``` takes precedence. | -| **x-ms-content-sha256** | base64 encoded SHA256 hash of the request body. It must be provided even if there is no body. ```base64_encode(SHA256(body))```| -| **Authorization** | Authentication information required by **HMAC-SHA256** scheme. Format and details are explained below. | - - -**Example:** -``` -Host: {myconfig}.azconfig.io -Date: Fri, 11 May 2018 18:48:36 GMT -x-ms-content-sha256: {SHA256 hash of the request body} -Authorization: HMAC-SHA256 Credential={Access Key ID}&SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature={Signature} -``` -# -# -# Authorization Header -# -**Syntax:** - -``Authorization``: **HMAC-SHA256** ```Credential```=\&```SignedHeaders```=\&```Signature```=\ -# -# -| Argument | Description | -| ------ | ------ | -| **HMAC-SHA256** | Authorization Scheme _(required)_ | -| **Credential** | The ID of the access key used to compute the Signature. _(required)_ | -| **SignedHeaders** | HTTP Request Headers added to the signature. _(required)_ | -| **Signature** | base64 encoded HMACSHA256 of **String-To-Sign**. _(required)_| - - -# -# -### Credential - -ID of the access key used to compute the **Signature**. - -# -### Signed Headers - -Semicolon separated HTTP request header names required to sign the request. These HTTP headers must be correctly provided with the request as well. **Don't use whitespaces**. - -**Required HTTP request headers**: - -```x-ms-date```[or ```Date```];```host```;```x-ms-content-sha256``` - -Any other HTTP request headers can also be added to the signing. Just append them to the ```SignedHeaders``` argument. - -**Example:** - -x-ms-date;host;x-ms-content-sha256;```Content-Type```;```Accept``` - -# -# -### Signature -Base64 encoded HMACSHA256 hash of the **String-To-Sign** using the access key identified by `Credential`. -```base64_encode(HMACSHA256(String-To-Sign, Secret))``` - -# -# -##### String-To-Sign -It represents canonical representation of the request: - -_String-To-Sign=_ - -**HTTP_METHOD** + '\n' + -**path_and_query** + '\n' + -**signed_headers_values** - - -| Argument | Description | -| ------ | ------ | -| **HTTP_METHOD** | Uppercased HTTP method name used with the request. See [section 9](https://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html) | -|**path_and_query** | Concatenation of request absolute URI path and query string. See [section 3.3](https://tools.ietf.org/html/rfc3986#section-3.3). -| **signed_headers_values** | Semicolon separated values of all HTTP request headers listed in **SignedHeaders**. The format follows **SignedHeaders** semantic. | - -**Example:** -```js -string-To-Sign= - "GET" + '\n' + // VERB - "/kv?fields=*&api-version=1.0" + '\n' + // path_and_query - "Fri, 11 May 2018 18:48:36 GMT;{myconfig}.azconfig.io;{value of ms-content-sha256 header}" // signed_headers_values -``` - -# -# -### **Errors** -# -# - -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256, Bearer -``` -**Reason:** Authorization request header with HMAC-SHA256 scheme is not provided. -**Solution:** Provide valid ```Authorization``` HTTP request header -# -# -# -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="The access token has expired", Bearer -``` -**Reason:** ```Date``` or ```x-ms-date``` request header is more than 15 minutes off from the current GMT time. -**Solution:** Provide correct date and time -# -# -# -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="Invalid access token date", Bearer -``` -**Reason:** Missing or invalid ```Date``` or ```x-ms-date``` request header -# -# -# -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="[Credential][SignedHeaders][Signature] is required", Bearer -``` -**Reason:** Missing a required parameter from ```Authorization``` request header -# -# -# -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="Invalid Credential", Bearer -``` -**Reason:** Provided [```Host```]/[Access Key ID] is not found. -**Solution:** Check the ```Credential``` parameter of the ```Authorization``` request header and make sure it is a valid Access Key ID. Make sure the ```Host``` header points to the registered account. -# -# -# -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="Invalid Signature", Bearer -``` -**Reason:** The ```Signature``` provided doesn't match what the server expects. -**Solution:** Make sure the ```String-To-Sign``` is correct. Make sure the ```Secret``` is correct and properly used (base64 decoded prior to using). See **Examples** section. -# -# -# -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="Signed request header 'xxx' is not provided", Bearer -``` -**Reason:** Missing request header required by ```SignedHeaders``` parameter in ```Authorization``` header. -**Solution:** Provide the required header with correct value. -# -# -# -```sh -HTTP/1.1 401 Unauthorized -WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="XXX is required as a signed header", Bearer -``` -**Reason:** Missing parameter in ```SignedHeaders```. -**Solution:** Check **Signed Headers** minimum requirements. -# -# -# - - - -## Code snippets - -### JavaScript -*Prerequisites*: [Crypto-JS](https://code.google.com/archive/p/crypto-js/) - -```js -function signRequest(host, - method, // GET, PUT, POST, DELETE - url, // path+query - body, // request body (undefined of none) - credential, // access key id - secret) // access key value (base64 encoded) -{ - var verb = method.toUpperCase(); - var utcNow = new Date().toUTCString(); - var contentHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Base64); - - // - // SignedHeaders - var signedHeaders = "x-ms-date;host;x-ms-content-sha256"; // Semicolon separated header names - - // - // String-To-Sign - var stringToSign = - verb + '\n' + // VERB - url + '\n' + // path_and_query - utcNow + ';' + host + ';' + contentHash; // Semicolon separated SignedHeaders values - - // - // Signature - var signature = CryptoJS.HmacSHA256(stringToSign, CryptoJS.enc.Base64.parse(secret)).toString(CryptoJS.enc.Base64); - - // - // Result request headers - return [ - { name: "x-ms-date", value: utcNow }, - { name: "x-ms-content-sha256", value: contentHash }, - { name: "Authorization", value: "HMAC-SHA256 Credential=" + credential + "&SignedHeaders=" + signedHeaders + "&Signature=" + signature } - ]; -} -``` - -### C# - -```cs -using (var client = new HttpClient()) -{ - var request = new HttpRequestMessage() - { - RequestUri = new Uri("https://{config store name}.azconfig.io/kv?api-version=1.0"), - Method = HttpMethod.Get - }; - - // - // Sign the request - request.Sign(, ); - - await client.SendAsync(request); -} - -static class HttpRequestMessageExtensions -{ - public static HttpRequestMessage Sign(this HttpRequestMessage request, string credential, byte[] secret) - { - string host = request.RequestUri.Authority; - string verb = request.Method.ToString().ToUpper(); - DateTimeOffset utcNow = DateTimeOffset.UtcNow; - string contentHash = Convert.ToBase64String(request.Content.ComputeSha256Hash()); - - // - // SignedHeaders - string signedHeaders = "date;host;x-ms-content-sha256"; // Semicolon separated header names - - // - // String-To-Sign - var stringToSign = $"{verb}\n{request.RequestUri.PathAndQuery}\n{utcNow.ToString("r")};{host};{contentHash}"; - - // - // Signature - string signature; - - using (var hmac = new HMACSHA256(secret)) - { - signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.ASCII.GetBytes(stringToSign))); - } - - // - // Add headers - request.Headers.Date = utcNow; - request.Headers.Add("x-ms-content-sha256", contentHash); - request.Headers.Authorization = new AuthenticationHeaderValue("HMAC-SHA256", $"Credential={credential}&SignedHeaders={signedHeaders}&Signature={signature}"); - - return request; - } -} - -static class HttpContentExtensions -{ - public static byte[] ComputeSha256Hash(this HttpContent content) - { - using (var stream = new MemoryStream()) - { - if (content != null) - { - content.CopyToAsync(stream).Wait(); - stream.Seek(0, SeekOrigin.Begin); - } - - using (var alg = SHA256.Create()) - { - return alg.ComputeHash(stream.ToArray()); - } - } - } -} -``` -### Java -```java -public CloseableHttpResponse signRequest(HttpUriRequest request, String credential, String secret) - throws IOException, URISyntaxException { - Map authHeaders = generateHeader(request, credential, secret); - authHeaders.forEach(request::setHeader); - - return httpClient.execute(request); -} - -private static Map generateHeader(HttpUriRequest request, String credential, String secret) - throws URISyntaxException, IOException { - String requestTime = GMT_DATE_FORMAT.format(new Date()); - - String contentHash = buildContentHash(request); - // SignedHeaders - String signedHeaders = "x-ms-date;host;x-ms-content-sha256"; - - // Signature - String methodName = request.getRequestLine().getMethod().toUpperCase(); - URIBuilder uri = new URIBuilder(request.getRequestLine().getUri()); - String scheme = uri.getScheme() + "://"; - String requestPath = uri.toString().substring(scheme.length()).substring(uri.getHost().length()); - String host = new URIBuilder(request.getRequestLine().getUri()).getHost(); - String toSign = String.format("%s\n%s\n%s;%s;%s", methodName, requestPath, requestTime, host, contentHash); - - byte[] decodedKey = Base64.getDecoder().decode(secret); - String signature = Base64.getEncoder().encodeToString(new HmacUtils(HMAC_SHA_256, decodedKey).hmac(toSign)); - - // Compose headers - Map headers = new HashMap<>(); - headers.put("x-ms-date", requestTime); - headers.put("x-ms-content-sha256", contentHash); - - String authorization = String.format("HMAC-SHA256 Credential=%s, SignedHeaders=%s, Signature=%s", - credential, signedHeaders, signature); - headers.put("Authorization", authorization); - - return headers; -} - -private static String buildContentHash(HttpUriRequest request) throws IOException { - String content = ""; - if (request instanceof HttpEntityEnclosingRequest) { - try { - StringWriter writer = new StringWriter(); - IOUtils.copy(((HttpEntityEnclosingRequest) request).getEntity().getContent(), writer, - StandardCharsets.UTF_8); - - content = writer.toString(); - } - finally { - ((HttpEntityEnclosingRequest) request).getEntity().getContent().close(); - } - } - - byte[] digest = new DigestUtils(SHA_256).digest(content); - return Base64.getEncoder().encodeToString(digest); -} -``` -### Golang -```golang -import ( - "bytes" - "crypto/hmac" - "crypto/sha256" - "encoding/base64" - "io/ioutil" - "net/http" - "strings" - "time" -) - -//SignRequest Setup the auth header for accessing Azure AppConfiguration service -func SignRequest(id string, secret string, req *http.Request) error { - method := req.Method - host := req.URL.Host - pathAndQuery := req.URL.Path - if req.URL.RawQuery != "" { - pathAndQuery = pathAndQuery + "?" + req.URL.RawQuery - } - - content, err := ioutil.ReadAll(req.Body) - if err != nil { - return err - } - req.Body = ioutil.NopCloser(bytes.NewBuffer(content)) - - key, err := base64.StdEncoding.DecodeString(secret) - if err != nil { - return err - } - - timestamp := time.Now().UTC().Format(http.TimeFormat) - contentHash := getContentHashBase64(content) - stringToSign := fmt.Sprintf("%s\n%s\n%s;%s;%s", strings.ToUpper(method), pathAndQuery, timestamp, host, contentHash) - signature := getHmac(stringToSign, key) - - req.Header.Set("x-ms-content-sha256", contentHash) - req.Header.Set("x-ms-date", timestamp) - req.Header.Set("Authorization", "HMAC-SHA256 Credential="+id+", SignedHeaders=x-ms-date;host;x-ms-content-sha256, Signature="+signature) - - return nil -} - -func getContentHashBase64(content []byte) string { - hasher := sha256.New() - hasher.Write(content) - return base64.StdEncoding.EncodeToString(hasher.Sum(nil)) -} - -func getHmac(content string, key []byte) string { - hmac := hmac.New(sha256.New, key) - hmac.Write([]byte(content)) - return base64.StdEncoding.EncodeToString(hmac.Sum(nil)) -} -``` -### Python -```python - -import base64 -import hashlib -import hmac -from datetime import datetime -import six - -def sign_request(host, - method, # GET, PUT, POST, DELETE - url, # Path + Query - body, # Request body - credential, # Access Key ID - secret): # Access Key Value - verb = method.upper() - - utc_now = str(datetime.utcnow().strftime("%b, %d %Y %H:%M:%S ")) + "GMT" - - if six.PY2: - content_digest = hashlib.sha256(bytes(body)).digest() - else: - content_digest = hashlib.sha256(bytes(body, 'utf-8')).digest() - - content_hash = base64.b64encode(content_digest).decode('utf-8') - - # Signed Headers - signed_headers = "x-ms-date;host;x-ms-content-sha256" # Semicolon separated header names - - # String-To-Sign - string_to_sign = verb + '\n' + \ - url + '\n' + \ - utc_now + ';' + host + ';' + content_hash # Semicolon separated SignedHeaders values - - # Decode secret - if six.PY2: - decoded_secret = base64.b64decode(secret) - digest = hmac.new(decoded_secret, bytes( - string_to_sign), hashlib.sha256).digest() - else: - decoded_secret = base64.b64decode(secret, validate=True) - digest = hmac.new(decoded_secret, bytes( - string_to_sign, 'utf-8'), hashlib.sha256).digest() - - # Signature - signature = base64.b64encode(digest).decode('utf-8') - - # Result request headers - return { - "x-ms-date": utc_now, - "x-ms-content-sha256": content_hash, - "Authorization": "HMAC-SHA256 Credential=" + credential + "&SignedHeaders=" + signed_headers + "&Signature=" + signature - } -``` -### PowerShell -```PowerShell -function Sign-Request( - [string] $hostname, - [string] $method, # GET, PUT, POST, DELETE - [string] $url, # path+query - [string] $body, # request body - [string] $credential, # access key id - [string] $secret # access key value (base64 encoded) -) -{ - $verb = $method.ToUpperInvariant() - $utcNow = (Get-Date).ToUniversalTime().ToString("R", [Globalization.DateTimeFormatInfo]::InvariantInfo) - $contentHash = Compute-SHA256Hash $body - - $signedHeaders = "x-ms-date;host;x-ms-content-sha256"; # Semicolon separated header names - - $stringToSign = $verb + "`n" + - $url + "`n" + - $utcNow + ";" + $hostname + ";" + $contentHash # Semicolon separated signedHeaders values - - $signature = Compute-HMACSHA256Hash $secret $stringToSign - - # Return request headers - return @{ - "x-ms-date" = $utcNow; - "x-ms-content-sha256" = $contentHash; - "Authorization" = "HMAC-SHA256 Credential=" + $credential + "&SignedHeaders=" + $signedHeaders + "&Signature=" + $signature - } -} - -function Compute-SHA256Hash( - [string] $content -) -{ - $sha256 = [System.Security.Cryptography.SHA256]::Create() - try { - return [Convert]::ToBase64String($sha256.ComputeHash([Text.Encoding]::ASCII.GetBytes($content))) - } - finally { - $sha256.Dispose() - } -} - -function Compute-HMACSHA256Hash( - [string] $secret, # base64 encoded - [string] $content -) -{ - $hmac = [System.Security.Cryptography.HMACSHA256]::new([Convert]::FromBase64String($secret)) - try { - return [Convert]::ToBase64String($hmac.ComputeHash([Text.Encoding]::ASCII.GetBytes($content))) - } - finally { - $hmac.Dispose() - } -} - -# Stop if any error occurs -$ErrorActionPreference = "Stop" - -$uri = [System.Uri]::new("https://{myconfig}.azconfig.io/kv?api-version=1.0") -$method = "GET" -$body = $null -$credential = "" -$secret = "" - -$headers = Sign-Request $uri.Authority $method $uri.PathAndQuery $body $credential $secret -Invoke-RestMethod -Uri $uri -Method $method -Headers $headers -Body $body -``` -### Bash -*Prerequisites*: - -| Prerequisite | Command | Versions Tested | -| ------------ | ------- | --------------- | -| [Bash](https://www.gnu.org/software/bash/) | bash | 3.5.27, 4.4.23 | -| [coreutils](https://www.gnu.org/software/coreutils/) | tr | 8.28 | -| [curl](https://curl.haxx.se/) | curl | 7.55.1, 7.58.0 | -| [OpenSSL](https://www.openssl.org/) | openssl | 1.1.0g, 1.1.1a | -| [util-linux](https://github.com/karelzak/util-linux/) | hexdump | 2.14.1, 2.31.1 | - -```Bash -#!/bin/bash - -sign_request () { - local host="$1" - local method="$2" # GET, PUT, POST, DELETE - local url="$3" # path+query - local body="$4" # request body - local credential="$5" # access key id - local secret="$6" # access key value (base64 encoded) - - local verb=$(printf "$method" | tr '[:lower:]' '[:upper:]') - local utc_now="$(date -u '+%a, %d %b %Y %H:%M:%S GMT')" - local content_hash="$(printf "$body" | openssl sha256 -binary | base64)" - - local signed_headers="x-ms-date;host;x-ms-content-sha256" # Semicolon separated header names - local string_to_sign="$verb\n$url\n$utc_now;$host;$content_hash" # Semicolon separated signed_headers values - - local decoded_secret="$(printf "$secret" | base64 -d | hexdump -v -e '/1 "%02x"')" - local signature="$(printf "$string_to_sign" | openssl sha256 -mac HMAC -macopt hexkey:"$decoded_secret" -binary | base64)" - - # Output request headers - printf '%s\n' \ - "x-ms-date: $utc_now" \ - "x-ms-content-sha256: $content_hash" \ - "Authorization: HMAC-SHA256 Credential=$credential&SignedHeaders=$signed_headers&Signature=$signature" -} - -host="{config store name}.azconfig.io" -method="GET" -url="/kv?api-version=1.0" -body="" -credential="" -secret="" - -headers=$(sign_request "$host" "$method" "$url" "$body" "$credential" "$secret") - -while IFS= read -r line; do - header_args+=("-H$line") -done <<< "$headers" -curl -X "$method" -d "$body" "${header_args[@]}" "https://$host$url" -``` \ No newline at end of file diff --git a/docs/REST/authentication/index.md b/docs/REST/authentication/index.md deleted file mode 100644 index 2d7f3e4b..00000000 --- a/docs/REST/authentication/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Authentication - -All HTTP requests must be authenticated. The following authentication schemes are supported. - -## HMAC - -[HMAC authentication](./hmac.md) uses a randomly generated secret to sign request payloads. Details on how requests using this authentication method are authorized can be found in the [HMAC authorization](../authorization/hmac.md) section. - -## Azure Active Directory - -[Azure Active Directory (AAD) authentication](./aad.md) utilizes a bearer token that is obtained from Azure Active Directory to authenticate requests. Details on how requests using this authentication method are authorized can be found in the [AAD authorization](../authorization/aad.md) section. \ No newline at end of file diff --git a/docs/REST/authorization/aad.md b/docs/REST/authorization/aad.md deleted file mode 100644 index bcec57c2..00000000 --- a/docs/REST/authorization/aad.md +++ /dev/null @@ -1,43 +0,0 @@ -# Azure Active Directory Authorization - REST API Reference - -When Azure Active Directory (AAD) authentication is used, authorization is handled by Azure Role Based Access Control (RBAC). Azure RBAC requires users to be assigned to roles in order to grant access to resources. Each role contains a set of actions that users assigned to the role will be able to perform. - -## Roles - -There following roles are built-in roles that are available in Azure subscriptions by default. - -### Azure App Configuration Data Owner - -This role provides full access to all operations. - -### Azure App Configuration Data Reader - -This role enables read operations. - -## Actions - -Roles contain a list of actions that users assigned to that role can perform. Azure App Configuration supports the following actions. - -### Microsoft.AppConfiguration/configurationStores/keyValues/read - -This action allows read access to App Configuration key-value resources such as /kv and /labels. - -### Microsoft.AppConfiguration/configurationStores/keyValues/write - -This action allows write access to App Configuration key-value resources. - -### Microsoft.AppConfiguration/configurationStores/keyValues/delete - -This action allows App Configuration key-value resources to be deleted. Note, deleting a resource returns the key-value that was deleted. - -## Errors - -```sh -HTTP/1.1 403 Forbidden -``` -**Reason:** The principal making the request does not have the required permissions to perform the requested operation. -**Solution:** Assign the role required to perform the requested operation to the principal making the request. - -## Managing Role Assignments - -Managing role assignments is done using [Azure RBAC](https://docs.microsoft.com/en-us/azure/role-based-access-control/overview) procedures that are standard across all Azure services. It is possible to do this through Azure CLI, PowerShell, the Azure Portal, and more. Official documentation on how to make role assignments can be found [here](https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-portal). \ No newline at end of file diff --git a/docs/REST/authorization/hmac.md b/docs/REST/authorization/hmac.md deleted file mode 100644 index 700ea0f2..00000000 --- a/docs/REST/authorization/hmac.md +++ /dev/null @@ -1,13 +0,0 @@ -# HMAC Authorization - REST API Reference - -When HMAC authentication is used, operations fall in to one of two categories, read or write. Read-write access keys grant permission to call all operations. Read-only access keys grant permission to call only read operations. Whether an access key is read-only or read-write is determined by its `readOnly` property. Any attempt to make a write request with a read-only access key will result in the request being unauthorized. - -The specification describing access keys and the API used to obtain them is detailed in the Azure App Configuration resource provider spec [here](https://github.com/Azure/azure-rest-api-specs/blob/master/specification/appconfiguration/resource-manager/Microsoft.AppConfiguration/stable/2019-10-01/appconfiguration.json). Access keys are obtained via the "ConfigurationStores_ListKeys" operation. - -## Errors - -```sh -HTTP/1.1 403 Forbidden -``` -**Reason:** The access key used to authenticate the request does not provide the required permissions to perform the requested operation. -**Solution:** Obtain an access key that provides permission to perform the requested operation and use it to authenticate the request. diff --git a/docs/REST/authorization/index.md b/docs/REST/authorization/index.md deleted file mode 100644 index 7ebda5c4..00000000 --- a/docs/REST/authorization/index.md +++ /dev/null @@ -1,11 +0,0 @@ -# Authorization - -Authorization refers to the procedure used to determine the permissions that a caller has when making a request. There are multiple authorization models. The authorization model that is used for a request depends on the [authentication](../authentication/index.md) method that is used. The authorization models are listed below. - -## HMAC - -The [authorization model](./hmac.md) model associated with HMAC authentication splits permissions into read-only or read-write. See the [HMAC authorization](./hmac.md) page for details. - -## Azure Active Directory - -The [authorization model](./aad.md) associated with Azure Active Directory (AAD) authentication uses Azure RBAC to control permissions. See the [AAD authorization](./aad.md) page for details. \ No newline at end of file diff --git a/docs/REST/consistency.md b/docs/REST/consistency.md deleted file mode 100644 index 1ae3dc52..00000000 --- a/docs/REST/consistency.md +++ /dev/null @@ -1,48 +0,0 @@ -# Real-time Consistency - REST API Reference -# -**Problem:** -Due to the nature of some distributed systems **real-time** consistency between requests can't (or it's very hard) to be enforced implicitly. A solution is to allow protocol support in the form of mutliple **Synchronization Tokens**. Synchronization tokens are optional. - -**Objectives:** -To guarantee **real-time** consistency between different client instances and requests. - -**Implementation:** - -Uses optional ``Sync-Token`` request/response headers. - -Syntax: -``` -Sync-Token: =;sn= -``` - - -|Parameter|| -| -- | -- | -| `````` | Token ID (opaque) | -| `````` | Token value (opaque). Allows base64 encoded string | -| `````` | Token sequence number (version). Higher means newer version of the same token. Allows for better concurrency and client cachability. The client may choose to use only token's last version, since token versions are inclusive. Not required for requests. | - -**Response:** - -The service provides a ``Sync-Token`` header with each response. - -``` -Sync-Token: jtqGc1I4=MDoyOA==;sn=28 -``` - -**Request:** - -Any subsequent request is guaranteed **real-time** consistent response in relation to the provided ``Sync-Token``. -``` -Sync-Token: = -``` - -If the ``Sync-Token`` header is omitted from the request, then it's possible for the service to respond with cached data during a short period of time (up to a few seconds), before it settles internally. This may cause inconsistent reads if changes have occurred immediately before reading. - - -**Mutiple Sync-Token** - -The server MAY respond with multiple sync-tokens for a single request. To keep **real-time** consistency for the next request, the client MUST respond with all of the received sync-tokens. Per RFC, multiple header values must be comma separated. -``` -Sync-Token: =,= -``` \ No newline at end of file diff --git a/docs/REST/headers.md b/docs/REST/headers.md deleted file mode 100644 index c44d3c02..00000000 --- a/docs/REST/headers.md +++ /dev/null @@ -1,37 +0,0 @@ -# Headers - REST API Reference -# - -## Request Headers - -The following table describes common request headers used in Azure App Configuration. - -| Header | Description | Example | -| -- | -- | -- | -| **Authorization** | Used to [authenticate](./authentication/index.md) a request to the service. See [section 14.8](https://tools.ietf.org/html/rfc2616#section-14.8) | `Authorization: HMAC-SHA256 Credential=&SignedHeaders=Host;x-ms-date;x-ms-content-sha256&Signature=` | -| **Accept** | Informs the server what media type the client will accept in an HTTP response. See [section 14.1](https://tools.ietf.org/html/rfc2616#section-14.1) | `Accept: application/vnd.microsoft.appconfig.kv+json;` | -| **Accept-Datetime** | Requests the server to return its content as a representation of its prior state. The value of this header is the requested datetime of that state. See [RFC 7089](https://tools.ietf.org/html/rfc7089#section-2.1.1) | `Accept-Datetime: Sat, 12 May 2018 02:10:00 GMT` | -| **Content-Type** | Contains the media-type of the content within the HTTP request body. See [section 14.17](https://tools.ietf.org/html/rfc2616#section-14.17) | `Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8;` | -| **Date** | The datetime that the HTTP request was issued. This header is used in [HMAC authentication](./authentication/hmac.md). See [section 14.18](https://tools.ietf.org/html/rfc2616#section-14.18) | `Date: Fri, 11 May 2018 18:48:36 GMT` | -| **Host** | Specifies the tenant for which the request has been issued. This header is used in [HMAC authentication](./authentication/hmac.md). See [section 14.23](https://tools.ietf.org/html/rfc2616#section-14.23) | `Host: contoso.azconfig.io` | -| **If-Match** | Used to make an HTTP request conditional. This request should only succeed if the targetted resource's etag matches the value of this header. The '*' value matches any etag. See [section 14.24](https://tools.ietf.org/html/rfc2616#section-14.24) | `If-Match: "4f6dd610dd5e4deebc7fbaef685fb903"` | -| **If-None-Match** | Used to make an HTTP request conditional. This request should only succeed if the targetted resource's etag does not match the value of this header. The '*' value matches any etag. See [section 14.26](https://tools.ietf.org/html/rfc2616#section-14.26) | `If-None-Match: "4f6dd610dd5e4deebc7fbaef685fb903"` | -| **Sync-Token** | Used to enable real-time consistency during a sequence of requests. | `Sync-Token: jtqGc1I4=MDoyOA==;sn=28` | -| **x-ms-client-request-id** | A unique id provided by the client used to track a request's round-trip. | `x-ms-client-request-id: 00000000-0000-0000-0000-000000000000` | -| **x-ms-content-sha256** | A sha256 digest of the HTTP request body. This header is used in [HMAC authentication](./authentication/hmac.md). | `x-ms-content-sha256: 47DEQpj8HBSa+/TImW+5JCeuQeRkm5NMpJWZG3hSuFU=` | -| **x-ms-date** | This header may be set and used in place of the `Date` header if the date header is unable to be accessed. This header is used in [HMAC authentication](./authentication/hmac.md). | `x-ms-date: Fri, 11 May 2018 18:48:36 GMT` | -| **x-ms-return-client-request-id** | Used in conjunction with the `x-ms-client-request-id` header. If the value of this header is 'true' then the server will be instructed to return the value of the `x-ms-client-request-id` request header. | `x-ms-return-client-request-id: true` | - -## Response Headers - -The server may include the following HTTP headers in its responses. - -| Header | Description | Example | -| -- | -- | -- | -| **Content-Type** | Contains the media-type of the content within the HTTP response body. See [section 14.17](https://tools.ietf.org/html/rfc2616#section-14.17) | `Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8;` | -| **ETag** | An opaque token representing the state of a given resource. Can be used in conditional operations. See [section 14.19](https://tools.ietf.org/html/rfc2616#section-14.19) | `ETag: "4f6dd610dd5e4deebc7fbaef685fb903"` | -| **Last-Modified** | Describes when the requested resource was last modified. Formatted as an [HTTP-Date](https://tools.ietf.org/html/rfc2616#section-3.3.1). See [section 14.29](https://tools.ietf.org/html/rfc2616#section-14.29) | `Last-Modified: Tue, 05 Dec 2017 02:41:26 GMT` | -| **Link** | Provides links to resources that are related to the response. This header is used for paging by using the _next_ link. See [RFC 5988](https://tools.ietf.org/html/rfc5988) | `Link: ; rel="next"` | -| **Memento-Datetime** | Indicates that the content contained in a response represents a prior state. The value of this header is the datetime of that state. See [RFC 7089](https://tools.ietf.org/html/rfc7089#section-2.1.1) | `Memento-Datetime: Sat, 12 May 2018 02:10:00 GMT` | -| **retry-after-ms** | Provides a suggested period (in milliseconds) for the client to wait before retrying a failed request. | `retry-after-ms: 10` | -| **x-ms-request-id** | A unique id generated by the server that is used to track the request within the service. | `x-ms-request-id: 00000000-0000-0000-0000-000000000000` | -| **WWW-Authenticate** | Used to challenge clients for authentication and provide a reason as to why an authentication attempt has failed. See [section 14.47](https://tools.ietf.org/html/rfc2616#section-14.47) | `WWW-Authenticate: HMAC-SHA256 error="invalid_token" error_description="Invalid Signature"` | diff --git a/docs/REST/keys.md b/docs/REST/keys.md deleted file mode 100644 index d711cb32..00000000 --- a/docs/REST/keys.md +++ /dev/null @@ -1,170 +0,0 @@ -# Keys - REST API Reference -api-version: 1.0 -# -**Represents Key resource**. - -``` -{ - "name": [string] // Name of the key -} -``` - -Supports the following operations: -- List - -For all operations ``name`` is an optional filter parameter. If ommited it implies **any** key. - - -# -# -**Prerequisites**: -- All HTTP requests must be authenticated. See the [authentication](./authentication/index.md) section. -- All HTTP requests must provide explicit ``api-version``. See the [versioning](./versioning.md) section. - -# -# -## List Keys -# -``` -GET /keys?api-version={api-version} HTTP/1.1 -``` -**Responses:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.keyset+json; charset=utf-8" -``` - -```sh -{ - "items": [ - { - "name": "{key-name}" - }, - ... - ], - "@nextLink": "{relative uri}" -} -``` - -# -# -# -## Pagination -# -The result is paginated if the number of items returned exceeds the response limit. Follow the optional ``Link`` response headers and use ``rel="next"`` for navigation. -Alternatively the content provides a next link in the form of the ``@nextLink`` property. The next link contains ``api-version`` parameter. -``` -GET /keys?api-version={api-version} HTTP/1.1 -``` -**Response:** -``` -HTTP/1.1 OK -Content-Type: application/vnd.microsoft.appconfig.keyset+json; charset=utf-8 -Link: <{relative uri}>; rel="next" -``` -``` -{ - "items": [ - ... - ], - "@nextLink": "{relative uri}" -} -``` - -# -# -# -## Filtering -# -Filtering by ```name``` is supported. - -``` -GET /keys?name={key-name}&api-version={api-version} -``` - -**Supported filters** - -|Key Name|| -|--|--| -|```name``` is omitted or ```name=*```|Matches **any** key| -|```name=abc```|Matches a key named **abc**| -|```name=abc*```|Matches key names that start with **abc**| -|```name=abc,xyz```|Matches key names **abc** or **xyz** (limited to 5 CSV)| - -***Reserved characters*** - -```*```, ```\```, ```,``` - -If a reserved character is part of the value, then it must be escaped using ```\{Reserved Character}```. Non-reserved characters can also be escaped. - - -***Filter Validation*** - -In the case of a filter validation error, the response is HTTP ```400``` with error details: - -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -``` -```sh -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "Invalid request parameter 'name'", - "name": "name", - "detail": "name(2): Invalid character", - "status": 400 -} -``` - -**Examples** - -- All -``` -GET /keys?api-version={api-version} -``` - -- Key name starts with **abc** -``` -GET /keys?name=abc*&api-version={api-version} -``` - -- Key name is either **abc** or **xyz** -``` -GET /keys?name=abc,xyz&api-version={api-version} -``` - -# -# -# -## Request specific fields -# -Use the optional ``$select`` query string parameter and provide comma separated list of requested fields. If the ``$select`` parameter is ommited, the response contains the default set. -``` -GET /keys?$select=name&api-version={api-version} HTTP/1.1 -``` - -# -# -# -## Time-Based Access -# -Obtain a representation of the result as it was at a past time. See section [2.1.1](https://tools.ietf.org/html/rfc7089#section-2.1) -``` -GET /keys&api-version={api-version} HTTP/1.1 -Accept-Datetime: Sat, 12 May 2018 02:10:00 GMT -``` - -**Response:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.keyset+json" -Memento-Datetime: Sat, 12 May 2018 02:10:00 GMT -Link: ; rel="original" -``` -``` -{ - "items": [ - .... - ] -} -``` \ No newline at end of file diff --git a/docs/REST/kv.md b/docs/REST/kv.md deleted file mode 100644 index c682cacc..00000000 --- a/docs/REST/kv.md +++ /dev/null @@ -1,382 +0,0 @@ -# Key-Value - REST API Reference -api-version: 1.0 -# -**Identity:** - -Key-Value is a resource identified by unique combination of ``key`` + ``label``. -``label`` is optional. To explicitly reference a key-value without a label use "\0" (url encoded as ``%00``). See details for each operation. - -**Operations:** -- Get -- List multiple -- Set -- Delete - -# -# -**Prerequisites:** -- All HTTP requests must be authenticated. See the [authentication](./authentication/index.md) section. -- All HTTP requests must provide explicit ``api-version``. See the [versioning](./versioning.md) section. - -# -# -## Syntax -# - -```sh -{ - "etag": [string] - "key": [string] - "label": [string, optional] - "content_type": [string, optional] - "value": [string] - "last_modified": [datetime ISO 8601] - "locked": [boolean] - "tags": [object with string properties, optional] -} -``` - - -# -# -## Get Key-Value -# -**Required:** ``{key}``, ``{api-version}`` -*Optional:* ``label`` - If ommited it implies a key-value without a label -``` -GET /kv/{key}?label={label}&api-version={api-version} -``` -**Responses:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8; -Last-Modified: Tue, 05 Dec 2017 02:41:26 GMT -ETag: "4f6dd610dd5e4deebc7fbaef685fb903" -``` - -```sh -{ - "etag": "4f6dd610dd5e4deebc7fbaef685fb903", - "key": "{key}", - "label": "{label}", - "content_type": null, - "value": "example value", - "last_modified": "2017-12-05T02:41:26+00:00", - "locked": "false", - "tags": { - "t1": "value1", - "t2": "value2" - } -} -``` -# -# -**If it doesn't exist** -``` -HTTP/1.1 404 Not Found -``` - -# -# -## Get (Conditionally) -To improve client caching, use ``If-Match`` or ``If-None-Match`` request headers. The ``etag`` argument is part of the key representation. See [Sec 14.24](https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html) - -**Get only if the current representation doesn't match the specified ``etag``** -``` -GET /kv/{key}?api-version={api-version} HTTP/1.1 -Accept: application/vnd.microsoft.appconfig.kv+json; -If-None-Match: "{etag}" -``` - -**Responses:** -``` -HTTP/1.1 304 NotModified -``` -or -``` -HTTP/1.1 200 OK -``` - -# -# -# -## List Key-Values -See **Filtering** for additional options -# -*Optional:* ``key`` - if not specified it implies **any** key. -*Optional:* ``label`` - if not specified it implies **any** label. - -``` -GET /kv?label=*&api-version={api-version} HTTP/1.1 -``` - -**Response:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kvset+json; charset=utf-8 -``` - -# -# -# -## Pagination -# -The result is paginated if the number of items returned exceeds the response limit. Follow the optional ``Link`` response headers and use ``rel="next"`` for navigation. -Alternatively the content provides a next link in form of the ``@nextLink`` property. The linked uri includes ``api-version`` argument. -``` -GET /kv?api-version={api-version} HTTP/1.1 -``` -**Response:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kvs+json; charset=utf-8 -Link: <{relative uri}>; rel="next" -``` -``` -{ - "items": [ - ... - ], - "@nextLink": "{relative uri}" -} -``` - - -# -# -# -## Filtering -# -A combination of ```key``` and ```label``` filtering is supported. -Use the optional ```key``` and ```label``` query string parameters. - -``` -GET /kv?key={key}&label={label}&api-version={api-version} -``` - -**Supported filters** - -|Key|| -|--|--| -|```key``` is omitted or ```key=*```|Matches **any** key| -|```key=abc```|Matches a key named **abc**| -|```key=abc*```|Matches keys names that start with **abc**| -|```key=abc,xyz```|Matches keys names **abc** or **xyz** (limited to 5 CSV)| - -|Label|| -|--|--| -|```label``` is omitted or ```label=*```|Matches **any** label| -|```label=%00```|Matches KV without label| -|```label=prod```|Matches the label **prod**| -|```label=prod*```|Matches labels that start with **prod**| -|```label=prod,test```|Matches labels **prod** or **test** (limited to 5 CSV)| - -***Reserved characters*** - -```*```, ```\```, ```,``` - -If a reserved character is part of the value, then it must be escaped using ```\{Reserved Character}```. Non-reserved characters can also be escaped. - - -***Filter Validation*** - -In the case of a filter validation error, the response is HTTP ```400``` with error details: - -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -``` -```sh -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "Invalid request parameter '{filter}'", - "name": "{filter}", - "detail": "{filter}(2): Invalid character", - "status": 400 -} -``` - -**Examples** - -- All -``` -GET /kv?api-version={api-version} -``` - -- Key name starts with **abc** and include all labels -``` -GET /kv?key=abc*&label=*&api-version={api-version} -``` - -- Key name starts with **abc** and label equals **v1** or **v2** -``` -GET /kv?key=abc*&label=v1,v2&api-version={api-version} -``` - -# -# -# -## Request specific fields -# -Use the optional ``$select`` query string parameter and provide comma separated list of requested fields. If the ``$select`` parameter is ommited, the response contains the default set. -``` -GET /kv?$select=key,value&api-version={api-version} HTTP/1.1 -``` - -# -# -# -## Time-Based Access -# -Obtain a representation of the result as it was at a past time. See section [2.1.1](https://tools.ietf.org/html/rfc7089#section-2.1). Pagination is still supported as defined above. -``` -GET /kv?api-version={api-version} HTTP/1.1 -Accept-Datetime: Sat, 12 May 2018 02:10:00 GMT -``` - -**Response:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kvset+json" -Memento-Datetime: Sat, 12 May 2018 02:10:00 GMT -Link: <{relative uri}>; rel="original" -``` -``` -{ - "items": [ - .... - ] -} -``` - -# -# -# -## Set Key -# -**Required:** ``{key}`` -*Optional:* ``label`` - if not specified or label=%00 it implies KV without label. -``` -PUT /kv/{key}?label={label}&api-version={api-version} HTTP/1.1 -Content-Type: application/vnd.microsoft.appconfig.kv+json -``` -```sh -{ - "value": "example value", // optional - "content_type": "user defined", // optional - "tags": { // optional - "tag1": "value1", - "tag2": "value2", - } -} -``` - -**Responses:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8 -Last-Modified: Tue, 05 Dec 2017 02:41:26 GMT -ETag: "4f6dd610dd5e4deebc7fbaef685fb903" -``` -```sh -{ - "etag": "4f6dd610dd5e4deebc7fbaef685fb903", - "key": "{key}", - "label": "{label}", - "content_type": "user defined", - "value": "example value", - "last_modified": "2017-12-05T02:41:26.4874615+00:00", - "tags": { - "tag1": "value1", - "tag2": "value2", - } -} -``` -# -# -**If the item is locked** -``` -HTTP/1.1 409 Conflict -Content-Type: application/problem+json; charset="utf-8" -``` -``` -{ - "type": "https://azconfig.io/errors/key-locked" - "title": "Modifing key '{key}' is not allowed", - "name": "{key}", - "detail": "The key is read-only. To allow modification unlock it first.", - "status": "409" -} -``` - -# -# -# Set Key (Conditionally) -To prevent race conditions, use ``If-Match`` or ``If-None-Match`` request headers. The ``etag`` argument is part of the key representation. -If ``If-Match`` or ``If-None-Match`` are omitted, the operation will be unconditional. - -**Update only if the current representation matches the specified ``etag``** -``` -PUT /kv/{key}?label={label}&api-version={api-version} HTTP/1.1 -Content-Type: application/vnd.microsoft.appconfig.kv+json -If-Match: "4f6dd610dd5e4deebc7fbaef685fb903" -``` -**Update only if the current representation doesn't match the specified ``etag``** -``` -PUT /kv/{key}?label={label}&api-version={api-version} HTTP/1.1 -Content-Type: application/vnd.microsoft.appconfig.kv+json; -If-None-Match: "4f6dd610dd5e4deebc7fbaef685fb903" -``` -**Update if any representation exist** -``` -PUT /kv/{key}?label={label}&api-version={api-version} HTTP/1.1 -Content-Type: application/vnd.microsoft.appconfig.kv+json; -If-Match: "*" -``` -**Add only if representation doesn't exist** -``` -PUT /kv/{key}?label={label}&api-version={api-version} HTTP/1.1 -Content-Type: application/vnd.microsoft.appconfig.kv+json -If-None-Match: "*" -``` - -# -# -**Responses** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8 -... -``` -or -``` -HTTP/1.1 412 PreconditionFailed -``` - -# -# -# -# Delete -# -**Required:** ``{key}``, ``{api-version}`` -*Optional:* ``{label}`` - if not specified or label=%00 it implies KV without label. -``` -DELETE /kv/{key}?label={label}&api-version={api-version} HTTP/1.1 -``` - -**Response:** -Return the deleted key-value or none if didn't exist. -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8 -... -``` -or -``` -HTTP/1.1 204 No Content -``` - -# -# -# Delete Key (Conditionally) -Similar to **Set Key (Conditionally)** diff --git a/docs/REST/labels.md b/docs/REST/labels.md deleted file mode 100644 index d137d616..00000000 --- a/docs/REST/labels.md +++ /dev/null @@ -1,170 +0,0 @@ -# Labels - REST API Reference -api-version: 1.0 -# -**Represents Label resource**. - -``` - { - "name": [string] // Name of the label -} -``` - -Supports the following operations: -- List - -For all operations ``name`` is an optional filter parameter. If ommited it implies **any** label. - - -# -# -**Prerequisites**: -- All HTTP requests must be authenticated. See the [authentication](./authentication/index.md) section. -- All HTTP requests must provide explicit ``api-version``. See the [versioning](./versioning.md) section. -# -# -## List Labels -# -``` -GET /labels?api-version={api-version} HTTP/1.1 -``` -**Responses:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.labelset+json; charset=utf-8" -``` - -```sh -{ - "items": [ - { - "name": "{label-name}" - }, - ... - ], - "@nextLink": "{relative uri}" -} -``` - -# -# -# -## Pagination -# -The result is paginated if the number of items returned exceeds the response limit. Follow the optional ``Link`` response headers and use ``rel="next"`` for navigation. -Alternatively the content provides a next link in form of the ``@nextLink`` property. The next link contains ``api-version`` parameter. -``` -GET /labels?api-version={api-version} HTTP/1.1 -``` -**Response:** -``` -HTTP/1.1 OK -Content-Type: application/vnd.microsoft.appconfig.labelset+json; charset=utf-8 -Accept-Ranges: items -Link: <{relative uri}>; rel="next" -``` -``` -{ - "items": [ - ... - ], - "@nextLink": "{relative uri}" -} -``` -# -# -# -## Filtering -# -Filtering by ```name``` is supported. - -``` -GET /labels?name={label-name}&api-version={api-version} -``` - -**Supported filters** - -|Key Name|| -|--|--| -|```name``` is omitted or ```name=*```|Matches **any** label| -|```name=abc```|Matches a label named **abc**| -|```name=abc*```|Matches label names that start with **abc**| -|```name=abc,xyz```|Matches label names **abc** or **xyz** (limited to 5 CSV)| - - -***Reserved characters*** - -```*```, ```\```, ```,``` - -If a reserved character is part of the value, then it must be escaped using ```\{Reserved Character}```. Non-reserved characters can also be escaped. - - -***Filter Validation*** - -In case of a filter validation error, the response is HTTP ```400``` with error details: - -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -``` -```sh -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "Invalid request parameter 'name'", - "name": "name", - "detail": "name(2): Invalid character", - "status": 400 -} -``` - -**Examples** - -- All -``` -GET /labels?api-version={api-version} -``` - -- Label name starts with **abc** -``` -GET /labels?name=abc*&api-version={api-version} -``` - -- Label name is either **abc** or **xyz** -``` -GET /labels?name=abc,xyz&api-version={api-version} -``` - -# -# -# -## Request specific fields -# -Use the optional ``$select`` query string parameter and provide comma separated list of requested fields. If the ``$select`` parameter is ommited, the response contains the default set. -``` -GET /labels?$select=name&api-version={api-version} HTTP/1.1 -``` - -# -# -# -## Time-Based Access -# -Obtain a representation of the result as it was at a past time. See section [2.1.1](https://tools.ietf.org/html/rfc7089#section-2.1) -``` -GET /labels&api-version={api-version} HTTP/1.1 -Accept-Datetime: Sat, 12 May 2018 02:10:00 GMT -``` - -**Response:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.labelset+json" -Memento-Datetime: Sat, 12 May 2018 02:10:00 GMT -Link: <{relative uri}>; rel="original" -``` -``` -{ - "items": [ - .... - ] -} -``` \ No newline at end of file diff --git a/docs/REST/locks.md b/docs/REST/locks.md deleted file mode 100644 index 0fd94f6b..00000000 --- a/docs/REST/locks.md +++ /dev/null @@ -1,102 +0,0 @@ -# Lock Key-Value - REST API Reference -api-version: 1.0 -# -**Provides lock/unlock semantics for the key-value resource.** - -Supports the following operations: -- Place lock -- Remove lock - -If present, ``label`` must be an explcit label value (**not** a wildcard). For all operations it's an optional parameter. If ommited it implies no label. - -# -# -**Prerequisites**: -- All HTTP requests must be authenticated. See the [authentication](./authentication/index.md) section. -- All HTTP requests must provide explicit ``api-version``. See the [versioning](./versioning.md) section. - -# -# -## Lock Key-Value -# -**Required:** ``{key}``, ``{api-version}`` -*Optional:* ``label`` -``` -PUT /locks/{key}?label={label}&api-version={api-version} HTTP/1.1 -``` -**Responses:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8" -``` - -```sh -{ - "etag": "4f6dd610dd5e4deebc7fbaef685fb903", - "key": "{key}", - "label": "{label}", - "content_type": null, - "value": "example value", - "created": "2017-12-05T02:41:26.4874615+00:00", - "locked": true, - "tags": [] -} -``` -# -# -**If the key-value doesn't exist** -``` -HTTP/1.1 404 Not Found -``` - -# -# -# -## Unlock Key-Value -# -**Required:** ``{key}``, ``{api-version}`` -*Optional:* ``label`` -``` -DELETE /locks/{key}?label={label}?api-version={api-version} HTTP/1.1 -``` -**Responses:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kv+json; charset=utf-8" -``` - -```sh -{ - "etag": "4f6dd610dd5e4deebc7fbaef685fb903", - "key": "{key}", - "label": "{label}", - "content_type": null, - "value": "example value", - "created": "2017-12-05T02:41:26.4874615+00:00", - "locked": true, - "tags": [] -} -``` -# -# -**If the key-value doesn't exist** -``` -HTTP/1.1 404 Not Found -``` - -# -# -# Conditional Lock/Unlock -To prevent race conditions, use ``If-Match`` or ``If-None-Match`` request headers. The ``etag`` argument is part of the key representation. -If ``If-Match`` or ``If-None-Match`` are omitted, the operation will be unconditional. - -**Apply operation only if the current key-value representation matches the specified ``etag``** -``` -PUT|DELETE /locks/{key}?label={label}&api-version={api-version} HTTP/1.1 -If-Match: "4f6dd610dd5e4deebc7fbaef685fb903" -``` -**Apply operation only if the current key-value representation exists, but doesn't match the specified ``etag``** -``` -PUT|DELETE /kv/{key}?label={label}&api-version={api-version} HTTP/1.1 -If-None-Match: "4f6dd610dd5e4deebc7fbaef685fb903" -``` \ No newline at end of file diff --git a/docs/REST/revisions.md b/docs/REST/revisions.md deleted file mode 100644 index 3200f8ee..00000000 --- a/docs/REST/revisions.md +++ /dev/null @@ -1,204 +0,0 @@ -# Key-Value Revisions - REST API Reference -api-version: 1.0 -# -**Defines chronological/historical representation of key-value resource(s).** -Revisions eventually expire (default 7 days) - -Supports the following operations: -- List - -For all operations ``key`` is an optional parameter. If ommited it implies **any** key. -For all operations ``label`` is an optional parameter. If ommited it implies **any** label. - -# -# -**Prerequisites**: -- All HTTP requests must be authenticated. See the [authentication](./authentication/index.md) section. -- All HTTP requests must provide explicit ``api-version``. See the [versioning](./versioning.md) section. - -# -# -## List Revisions -# -``` -GET /revisions?label=*&api-version={api-version} HTTP/1.1 -``` -**Responses:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.kvset+json; charset=utf-8" -Accept-Ranges: items -``` - -```sh -{ - "items": [ - { - "etag": "4f6dd610dd5e4deebc7fbaef685fb903", - "key": "{key}", - "label": "{label}", - "content_type": null, - "value": "example value", - "last_modified": "2017-12-05T02:41:26.4874615+00:00", - "tags": [] - }, - ... - ], - "@nextLink": "{relative uri}" -} -``` - -# -# -# -## Pagination -# -The result is paginated if the number of items returned exceeds the response limit. Follow the optional ``Link`` response header and use ``rel="next"`` for navigation. -Alternatively the content provides a next link in form of the ``@nextLink`` property. -``` -GET /revisions?api-version={api-version} HTTP/1.1 -``` -**Response:** -``` -HTTP/1.1 OK -Content-Type: application/vnd.microsoft.appconfig.kvs+json; charset=utf-8 -Accept-Ranges: items -Link: <{relative uri}>; rel="next" -``` -``` -{ - "items": [ - ... - ], - "@nextLink": "{relative uri}" -} -``` -# -# -# -## List subset of revisions -# -Use ``Range`` request header. The response will contain ``Content-Range`` header. -If the server can't satisfy the requested range it will respond with HTTP ``416`` (RangeNotSatisfiable) -``` -GET /revisions?api-version={api-version} HTTP/1.1 -Range: items=0-2 -``` -**Response** -``` -HTTP/1.1 206 Partial Content -Content-Type: application/vnd.microsoft.appconfig.revs+json; charset=utf-8 -Content-Range: items 0-2/80 -``` - -# -# -# -## Filtering -# -A combination of ```key``` and ```label``` filtering is supported. -Use the optional ```key``` and ```label``` query string parameters. - -``` -GET /revisions?key={key}&label={label}&api-version={api-version} -``` - -**Supported filters** - -|Key|| -|--|--| -|```key``` is omitted or ```key=*```|Matches **any** key| -|```key=abc```|Matches a key named **abc**| -|```key=abc*```|Matches keys names that start with **abc**| -|```key=*abc```|Matches keys names that end with **abc**| -|```key=*abc*```|Matches keys names that contain **abc**| -|```key=abc,xyz```|Matche keys names **abc** or **xyz** (limited to 5 CSV)| - -|Label|| -|--|--| -|```label``` is omitted or ```label=```|Matches entry without label| -|```label=*```|Matches **any** label| -|```label=prod```|Matches the label **prod**| -|```label=prod*```|Matches labels that start with **prod**| -|```label=*prod```|Matches labels that end with **prod**| -|```label=*prod*```|Matches labels that contain **prod**| -|```label=prod,test```|Matches labels **prod** or **test** (limited to 5 CSV)| - - -***Reserved characters*** - -```*```, ```\```, ```,``` - -If a reserved character is part of the value, then it must be escaped using ```\{Reserved Character}```. Non-reserved characters can also be escaped. - - -***Filter Validation*** - -In case of a filter validation error, the response is HTTP ```400``` with error details: - -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -``` -```sh -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "Invalid request parameter '{filter}'", - "name": "{filter}", - "detail": "{filter}(2): Invalid character", - "status": 400 -} -``` - -**Examples** - -- All -``` -GET /revisions -``` - -- Items where key name starts with **abc** -``` -GET /revisions?key=abc*&api-version={api-version} -``` - -- Items where key name is either **abc** or **xyz** and labels contain **prod** -``` -GET /revisions?key=abc,xyz&label=*prod*&api-version={api-version} -``` - -# -# -# -## Request specific fields -# -Use the optional ``$select`` query string parameter and provide comma separated list of requested fields. If the ``$select`` parameter is ommited, the response contains the default set. -``` -GET /revisions?$select=value,label,last_modified&api-version={api-version} HTTP/1.1 -``` - -# -# -# -## Time-Based Access -# -Obtain a representation of the result as it was at a past time. See section [2.1.1](https://tools.ietf.org/html/rfc7089#section-2.1) -``` -GET /revisions?api-version={api-version} HTTP/1.1 -Accept-Datetime: Sat, 12 May 2018 02:10:00 GMT -``` - -**Response:** -``` -HTTP/1.1 200 OK -Content-Type: application/vnd.microsoft.appconfig.revs+json" -Memento-Datetime: Sat, 12 May 2018 02:10:00 GMT -Link: <{relative uri}>; rel="original" -``` -``` -{ - "items": [ - .... - ] -} -``` \ No newline at end of file diff --git a/docs/REST/throttling.md b/docs/REST/throttling.md deleted file mode 100644 index 3df2e88d..00000000 --- a/docs/REST/throttling.md +++ /dev/null @@ -1,43 +0,0 @@ -# Throttling - REST API Reference -# -Configuration stores have limits on the requests that they may serve. Any requests that exceeds an allotted quota for a configuration store will receive an HTTP 429 (Too Many Requests) response. - -Throttling is divided into different quota policies: - -**Total Requests** - total number of requests - -**Total Bandwidth** - outbound data in bytes - -**Storage** - total storage size of user data in bytes - - -## Handling Throttled Responses - -When the rate limit for a given quota has been reached, the server will respond to further requests of that type with a _429_ status code. The _429_ response will contain a _retry-after-ms_ header providing the client with a suggested wait time (in milliseconds) to allow the request quota to replinish. - -``` -HTTP/1.1 429 (Too Many Requests) -retry-after-ms: 10 -Content-Type: application/problem+json; charset=utf-8 -``` -```sh -{ - "type": "https://azconfig.io/errors/too-many-requests", - "title": "Resource utilization has surpassed the assigned quota", - "policy": "Total Requests", - "status": 429 -} -``` - -In the above example, the client has exceeded its allowed quota and is advised to slow down and wait 10 milliseconds before attempting any further requests. Clients should consider progressive backoff as well. - - -# Other Retry -# -The service may identify situations other than throttling that need a client retry (ex: 503 Service Unavailable). -In all such cases, the ``retry-after-ms`` response header will be provided. To increase robustness, the client is advised to follow the suggested interval and perform a retry. - -``` -HTTP/1.1 503 Service Unavailable -retry-after-ms: 787 -``` \ No newline at end of file diff --git a/docs/REST/versioning.md b/docs/REST/versioning.md deleted file mode 100644 index 4a55b808..00000000 --- a/docs/REST/versioning.md +++ /dev/null @@ -1,71 +0,0 @@ -# Versioning - REST API Reference -api-version: 1.0 -# -Each client request must provide explicit API Version as query string parameter. For example: -``` - https://{myconfig}.azconfig.io/kv?api-version=1.0 -``` - -``api-version`` is expressed in semver (major.minor) format. Range or version negotiation is not supported. - - -## Error response - -The following outlines a summary of the possible error responses returned by the server when the requested API version can't be matched. - -# -#### API Version Unspecified -When a client makes a request without providing an API version. -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "API version is not specified", - "name": "api-version", - "detail": "An API version is required, but was not specified.", - "status": 400 -} -``` -# -#### Unsupported API Version -When a client requested API version does not match any of the supported API versions by the server. -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "Unsupported API version", - "name": "api-version", - "detail": "The HTTP resource that matches the request URI '{request uri}' does not support the API version '{api-version}'.", - "status": 400 -} -``` -# -#### Invalid API Version -When a client makes a request with an API version, but the value is malformed or cannot be parsed by the server. -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "Invalid API version", - "name": "api-version", - "detail": "The HTTP resource that matches the request URI '{request uri}' does not support the API version '{api-version}'.", - "status": 400 -} -``` -# -#### Ambiguous API Version -When a client requests API version that is ambiguous to the server. For example multiple different values. -``` -HTTP/1.1 400 Bad Request -Content-Type: application/problem+json; charset=utf-8 -{ - "type": "https://azconfig.io/errors/invalid-argument", - "title": "Ambiguous API version", - "name": "api-version", - "detail": "The following API versions were requested: {comma separated api versions}. At most, only a single API version may be specified. Please update the intended API version and retry the request.", - "status": 400 -} -``` \ No newline at end of file From 5077c3a6a366c966cb2c3fee30a611d75af1a70e Mon Sep 17 00:00:00 2001 From: Zhenlan Wang Date: Tue, 10 Nov 2020 22:50:12 -0800 Subject: [PATCH 2/3] Update for feedback --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f526f248..8210478d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,9 @@ Subscribe to the following repo to be notified of announcements and updates abou ## REST API Reference -Access full functionalities of App Configuration via its [REST APIs]((https://docs.microsoft.com/azure/azure-app-configuration/rest-api)) from any programming languages including those that don't have SDKs or development tools you often use. +Follow links below for the Azure App Configuration REST API reference + * [Key-vaule data operation](https://docs.microsoft.com/azure/azure-app-configuration/rest-api) + * [Configuration store management](https://docs.microsoft.com/rest/api/appconfiguration/) ## Client Libraries From f5aa36b91557e99b46844a50db69319f5a1fd889 Mon Sep 17 00:00:00 2001 From: Zhenlan Wang Date: Wed, 11 Nov 2020 11:58:05 -0800 Subject: [PATCH 3/3] Update for feedback --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8210478d..90ca3bd2 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Subscribe to the following repo to be notified of announcements and updates abou ## REST API Reference -Follow links below for the Azure App Configuration REST API reference +Follow the links below for the Azure App Configuration REST API reference. * [Key-vaule data operation](https://docs.microsoft.com/azure/azure-app-configuration/rest-api) * [Configuration store management](https://docs.microsoft.com/rest/api/appconfiguration/)