MS Certificering: AZ-204, overnieuw beginnen, deel 08

Ingediend door Dirk Hornstra op 21-dec-2021 15:35

Na het 1e blok: Azure App Service web apps (link), het 2e blok: Implement Azure Functions (link), het 3e blok: Develop solutions that use Blob Storage (link) en het 4e blok: Develop solutions that use Azure Cosmos DB (link), het 5e blok: Implement infrastructure as a service solutions: (link), het 6e blok Implement user authentication and authorization (link), het 7e blok Implement secure cloud solutions (link) is het nu tijd voor het 8e learning-block: Implement API Management, link.

Voor de verandering bestaat dit learning-block uit 1 module: Explore API Management.

API Management helpt organisaties om hun API's publiek beschikbaar te maken en intern om ontwikkelaars om de potentie van hun data en diensten beschikbaar te stellen.

API Management biedt business insights, analytics, beveiliging.

Een API gateway is het eindpunt wat de aanvragen accepteert en doorstuurt naar het backend-systeem, controleert API-keys, JWT-tokens, certificaten en andere credentials, dwingt quota's en rate limieten af, transformeert je API "on the fly" zonder dat jij code aan hoeft te passen, zorgt voor caching van backend antwoorden en logt metadata wat voor analytische processen gebruikt kan worden.

In de Azure portal kun je jouw API inrichten. Je definieert (of importeert) API schema's, de groepeert API's naar een product, stelt policies in voor quota en transformaties op de API, hier kun je inzicht krijgen in de analytics en je kunt de gebruikers beheren.

In de Developer portal kunnen ontwikkelaars de API documentatie lezen, via de interactieve console API's testen, een account aanmaken en abonneren om een API key te krijgen en analytics op hun eigen gebruik te bekijken.

Ontwikkelaars zien een product, die kan uit meerdere API's bestaan. API's zijn geconfigureerd met een titel, omschrijving en gebruikersvoorwaarden. Producten kunnen "open" en "protected" zijn. Voor protected heb je een subscription nodig, voor open niet. Goedkeuring van een subscription is op product-level geconfigureerd en kan ingesteld zijn dat een administrator het moet goedkeuren of dat een aanvraag "automatisch" geaccordeerd wordt.

API Management heeft deze vaste groepen:

  • Administrators, beheer van API management.
  • Developers, klanten die met jouw API's applicaties bouwen.
  • Guests, niet geauthenticeerde gebruikers, prospects of een API management instantie. Kunnen ingesteld worden met read-only toegang, waardoor ze API's wel kunnen bekijken, maar niet uitvoeren.


Administrators kunnen eigen groepen aanmaken of externe groepen in andere tenants.

Developers kunnen aangemaakt worden, uitgenodigd door administrators of zichzelf aanmelden via het developer portal. Elke developer is lid van 1 of meer groepen.

Met policies kun je via configuratie zaken aanpassen. Populaire statements zijn het omzetten van XML naar JSON en het uitvoeren van rate limiting om het aantal inkomende calls van developers binnen de perken te houden.

Je oplossing kan meerdere front- en back-end services bevatten. Hoe weet een client welk endpoint aangeroepen moet worden? En als er nieuwe services beschikbaar komen? Hoe handelen deze SSL terminatie, authenticatie en andere zaken af? Daar kan een API gateway bij helpen.

De API gateway zit tussen de client en de services. Het is een reverse proxy. Je kunt de diensten ook rechtstreeks aanbieden. Maar wat geeft dit voor problemen?

  • Mogelijk complexe code. Je client moet meerdere endpoints in de gaten houden en fouten correct afhandelen.
  • Het koppelt je front- en back-end. De client moet weten welke diensten de back-end aanbiedt. Refactoren van de back-end begint nu wel een stuk moeilijker te worden.
  • Een enkele actie moet mogelijk meerdere services aanroepen. Waardoor je mogelijk meerdere netwerk round-trips uitvoert, extra latency.
  • Elke service die publiekelijk beschikbaar is moet authenticatie, SSL en client rate limiting afhandelen.
  • Services moeten naar buiten een "vriendelijk" protocol gebruiken, zoals HTTP of WebSocket. Dit limiteert de protocollen die je kunt gebruiken.
  • Services met publieke endpoints zijn een mogelijk aanvalspunt en moeten dus flink dichtgetimmerd worden.

Een gateway zorgt voor een oplossing. Hiermee koppel je clients los van de services.

  • Gateway routing: gebruik als een reverse proxy om requests naar 1 of meer back-end services te sturen, op basis van layer-7 routing. Het is een enkel endpoint voor de client, waardoor zaken losgekoppeld worden.
  • Gateway aggregation: gebruik de gateway om meerdere losse requests te groeperen naar één request. Één aanvraag van de client kan resulteren in meerdere requests naar back-end services. De resultaten daarvan groepeer je in één respons naar de client.
  • Gateway offloading: functies die anders jouw service moet implementeren, kun je nu door de gateway laten afhandelen. Hierdoor "vergeet" je het ook niet bij één van de diensten.


En wat kun je de gateway dan laten doen? Nou, dat is een mooi lijstje:

  • SSL termination
  • Authentication
  • IP allow/block list
  • Client rate limiting (throttling)
  • Logging and monitoring
  • Response caching
  • GZIP compression
  • Servicing static content


Een policy is een XML-bestand die deze structuur volgt:


<policies>
  <inbound>
    <!-- statements to be applied to the request go here -->
  </inbound>
  <backend>
    <!-- statements to be applied before the request is forwarded to
         the backend service go here -->
  </backend>
  <outbound>
    <!-- statements to be applied to the response go here -->
  </outbound>
  <on-error>
    <!-- statements to be applied if there is an error condition go here -->
  </on-error>
</policies>

Even een aantal voorbeelden:


// inbound policy

<policies>
    <inbound>
        <cross-domain />
        <base />
        <find-and-replace from="xyz" to="abc" />
    </inbound>
</policies>

// voorbeeld om JSON terug te geven en bepaalde items uit je resultaat te filteren

<policies>
  <inbound>
    <base />
  </inbound>
  <backend>
    <base />
  </backend>
  <outbound>
    <base />
    <choose>
      <when condition="@(context.Response.StatusCode == 200 && context.Product.Name.Equals("Starter"))">
        <!-- NOTE that we are not using preserveContent=true when deserializing response body stream into a JSON object since we don't intend to access it again. See details on https://docs.microsoft.com/azure/api-management/api-management-transformation-policies#SetBody -->
        <set-body>
          @{
            var response = context.Response.Body.As<JObject>();
            foreach (var key in new [] {"minutely", "hourly", "daily", "flags"}) {
            response.Property (key).Remove ();
           }
          return response.ToString();
          }
    </set-body>
      </when>
    </choose>    
  </outbound>
  <on-error>
    <base />
  </on-error>
</policies>

Het voorgaande was een simpele policy. We gaan daar wat dieper op in;

  • Control flow, voer bepaalde acties op basis van "if ... then", dus boolean expressies.
  • Forward request, stuur het request door naar de backend service.
  • Limit concurrency, zorgt dat de policy niet meer dan x keer gelijktijdig wordt uitgevoerd.
  • Log to Event Hub, stuur berichten in een specifiek formaat naar Event Hub (ingesteld via Logger entity).
  • Mock response, stop met pipeline executie en stuur een mocked resultaat rechtstreeks terug naar de aanvrager.
  • Retry, probeer de policy nogmaals uit te voeren, zolang de voorwaarde daarvoor geldig is. Het uitvoeren zal herhaald worden op de ingestelde tijdsinterval en tot het aantal wat ingesteld is bij retry count.

 


// Voorbeeld control flow:


<choose>
    <when condition="Boolean expression | Boolean constant">
        <!— one or more policy statements to be applied if the above condition is true  -->
    </when>
    <when condition="Boolean expression | Boolean constant">
        <!— one or more policy statements to be applied if the above condition is true  -->
    </when>
    <otherwise>
        <!— one or more policy statements to be applied if none of the above conditions are true  -->
</otherwise>
</choose>

// Voorbeeld forward request:

<forward-request timeout="time in seconds" follow-redirects="true | false"/>

// Voorbeeld limit concurrency:

<limit-concurrency key="expression" max-count="number">
        <!— nested policy statements -->
</limit-concurrency>

// Voorbeeld loggen naar event hub:

<log-to-eventhub logger-id="id of the logger entity" partition-id="index of the partition where messages are sent" partition-key="value used for partition assignment">
  Expression returning a string to be logged
</log-to-eventhub>

// Voorbeeld mock response:

<mock-response status-code="code" content-type="media type"/>

// Voorbeeld retry:

<retry>
    condition="boolean expression or literal"
    count="number of retry attempts"
    interval="retry interval in seconds"
    max-interval="maximum retry interval in seconds"
    delta="retry interval delta in seconds"
    first-fast-retry="boolean expression or literal">
        <!-- One or more child policies. No restrictions -->
</retry>

// Voorbeeld return response:

<return-response response-variable-name="existing context variable">
  <set-header/>
  <set-body/>
  <set-status/>
</return-response>

Hier kun je meer voorbeelden vinden van policies (link) en hier vind je uitleg over het afhandelen van errors: link.

Het gebruik van abonnementen (subscriptions) is top. Want als iets binnenkomt zonder subscription, dan wordt je back-end service niet aangeroepen.

Je hebt verschillende scopes voor abonnementen, all APIs - elke API van die gateway, Single API - 1 API en alle bijbehorende endpoints, Product - collectie van 1 of meer API's die je in API management beheert. Een API kan aan meerdere producten gekoppeld zitten. Producten kunnen andere toegangsregels, gebruikers quota's en gebruikersvoorwaarden hebben.

Als je een API aanroept met een subscription-key, dan voeg je deze toe in de header met de naam Ocp-Apim-Subscription-Key of als dat niet kan via de querystring parameter subscription-key

Als je deze waarde niet mee stuurt krijg je een 401 terug.

Je kunt de API management gateway ook instellen om alleen bepaalde TLS certificaten te accepteren.

De API kan controleren op Certificate Authority (CA): alleen gesigneerd door een bepaalde, vertrouwde CA, Thumbprint: vertrouw alleen certificaten met een bepaalde thumbprint, Subject: sta alleen certificaten met een bepaald onderwerp toe, Expiration Date: vertrouw alleen certificaten die nog niet verlopen zijn.

Je kunt deze mixen en daar jouw criteria op in te stellen. Er zijn 2 standaard manieren om een certificaat te valideren: controleer wie het certificaat uitgeleverd heeft. Bekend? Dan is het OK. In de Azure Portal kun je de trusted certificate authorities instellen.
En/of controleer of het certificaat afkomstig is van een partner, de self-signed certificates.

Onder Custom domains kun je instellen dat Request client certificate op YES staat.

Voorbeeld hoe je in policies zaken controleert:


// controleer de thumbprint


<choose>
    <when condition="@(context.Request.Certificate == null || context.Request.Certificate.Thumbprint != "desired-thumbprint")" >
        <return-response>
            <set-status code="403" reason="Invalid client certificate" />
        </return-response>
    </when>
</choose>

// controleer de thumbprint op basis van geuploade certificaten

<choose>
    <when condition="@(context.Request.Certificate == null || !context.Request.Certificate.Verify()  || !context.Deployment.Certificates.Any(c => c.Value.Thumbprint == context.Request.Certificate.Thumbprint))" >
        <return-response>
            <set-status code="403" reason="Invalid client certificate" />
        </return-response>
    </when>
</choose>

// en controleer de uitgever en het subject/onderwerp

<choose>
    <when condition="@(context.Request.Certificate == null || context.Request.Certificate.Issuer != "trusted-issuer" || context.Request.Certificate.SubjectName.Name != "expected-subject-name")" >
        <return-response>
            <set-status code="403" reason="Invalid client certificate" />
        </return-response>
    </when>
</choose>

Hierna volgt een oefening om een backend API aan te maken:


// maak een API management instantie

myApiName=az204-apim-$RANDOM
myLocation=<myLocation>
myEmail=<myEmail>

// maak een resource group

az group create --name az204-apim-rg --location $myLocation

// maak een APIM instantie

az apim create -n $myApiName \
    --location $myLocation \
    --publisher-email $myEmail  \
    --resource-group az204-apim-rg \
    --publisher-name AZ204-APIM-Exercise \
    --sku-name Consumption

Vervolgens ga je in de Azure Portal naar de APIM manager, APIs en dan toevoegen.

Bij het veld OpenAPI specification geef je aan op welke URL de echte code draait.

In het veld API URL suffix geef je aan onder welke /sub je deze API aan kunt roepen.

Op het tabblad Settings haal je het vinkje weg bij "Subscription required".

Vervolgens kun je op tabblad Test controleren of het werkt.