Sensible Werte schützen

Abgeschlossen

Da es immer mehr Integrationen in externe Systeme gibt, ist es im AL-Code nicht unüblich, mit Geheimnissen wie Anmeldeinformationen und anderen sensiblen Textwerten zu arbeiten. Da es notwendig ist, diese vor der Aufdeckung durch Debuggen zu schützen. Letzteres wurde häufig durch Richtlinien zur Ressourcengefährdung auf Kosten einer einfachen Problembehandlung blockiert.

Führen wir einen neuen Typ SecretText für Variablen ein, um das Debuggen zu ermöglichen und gleichzeitig Anmeldeinformationen und andere vertrauliche Textwerte vor der Offenlegung zu schützen. Zudem erhalten einige der gängigen Szenarios in der System-App Unterstützung für die Übergabe von SecretText-Parametern für Anmeldeinformationen. Beispielsweise die Typen „HttpClient“ und „Isolated Storage“

Datentyp „SecretText“ dient dazu, vertrauliche Werte vor der Offenlegung durch den AL-Debugger beim regulären oder Debuggen von Momentaufnahmen zu schützen. Die Verwendung wird für Anwendungen empfohlen, die jegliche Art von Anmeldeinformationen wie API-Schlüssel, angepasste Lizenztokens oder Ähnliches verarbeiten müssen.

Abruf

Wenn eine Anmeldeinformation nicht durch das Attribut NonDebuggable in einem Prozedurbereich oder in der Variablen, in der sie enthalten ist, geschützt ist, gibt es die Gefahr, dass sie in einer Debugging-Sitzung oder einer Momentaufnahme seine gesamte Lebensdauer im AL-Code offengelegt. Dieses Leben kann in drei verschiedene Phasen aufgeteilt werden.

Ein Leistungsnachweis kann auf verschiedene Arten abgerufen werden:

  1. Ein API-Schlüssel wird durch einen Aufruf über den AL HttpClient abgerufen und dann als Authentifizierung für weitere Aufrufe genutzt.

  2. Ein Token wird über ein Steuerelement-Add-In abgerufen, das eine Integration mit einem Authentifizierungsanbieter wie OAUTH2 implementiert.

  3. Ein benutzerdefiniertes, vom Entwickler festgelegtes Szenario erstellt ein Authentifizierungstoken.

  4. Ein Entwickler codiert versehentlich zu Testzwecken eine Anmeldeinformation fest in den Code und vergisst dabei, diese zu entfernen.

Jeder Wert vom Typ Text oder Code kann einem Wert SecretText zugewiesen werden. Wenn die Token abgerufen und dann im Rahmen eines nicht debuggbaren Verfahrens in einen SecretText-Wert konvertiert werden, sind sie während ihrer Lebensdauer vor dem Debugger geschützt. Zudem garantiert der AL-Compiler, dass ein fest codierter Berechtigungsnachweis nicht direkt einem Ziel vom Typ SecretText zugewiesen werden kann.

[NonDebuggable]

procedure RetrieveSessionToken(Credential: SecretText; TargetUri: Text) SessionToken : SecretText
  var
    Request: HttpRequestMessage;
    Response: HttpResponseMessage;
    Client: HttpClient;
    Headers: HttpHeaders;
    Content: HttpContent;
  begin
    Request.SetRequestUri(TargetUri);
    Request.GetHeaders(Headers);

    // Compose an authorization header with a secret value
    Headers.Add('Authorization', SecretStrSubstNo('Bearer %1', Credential));
    Client.Send(Request, Response);

    ParseSessionToken(Response, SessionToken);
    exit;
  end;

[NonDebuggable]

procedure ParseSessionToken(Response: HttpResponseMessage; var SessionToken: SecretText) : Text
  begin
    // Parse the response
  end;

Transit

Ein Leistungsnachweis durchläuft den AL-Code, um die Punkte zu erreichen, an denen sie verwendet wird. Der Transit beinhaltet:

  • Variablen zuweisen

  • Als Parameter verwenden, um eine Prozedur/einen Trigger aufzurufen

  • Werden Sie zum Rückgabewert eines Funktionsaufrufs

Der SecretText-Datentyp garantiert, dass der Wert nicht debuggbar bleibt, indem er alle Zuweisungen von sich selbst zu einem debuggbaren Typ verhindert. Diese Einschränkung umfasst den Variant-Datentyp. Als Ergebnis ist das NonDebuggable-Attribut nur während des Abrufs erforderlich und kann für den Rest der Lebensdauer einer Anmeldeinformation weggelassen werden, da alle Zwischenziele automatisch geschützt sind.

procedure Assignments()
  var
    PlainText: Text;
    Credential: SecretText;
  begin
    Credential := PlainText; // Allowed
    PlainText := Credential; // Blocked

    ConsumePlainText(PlainText); // Allowed
    ConsumePlainText(Credential); // Blocked

    Credential := ProduceCredential(); //Allowed
    PlainText := ProduceCredential(); // Blocked
  end;

procedure ConsumePlainText(PlainText: Text)
  begin
  end;

procedure ProduceCredential(): SecretText
  begin
  end;

Verbrauch

Der Leistungsnachweis wird verbraucht, wenn sie zum Ausführen eines Vorgangs verwendet werden. Ein häufiges Beispiel ist die Kommunikation mit einem externen Webdienst über den AL HttpClient, bei der möglicherweise folgende Schritte erforderlich sind:

  1. Erstellen eines Authentifizierungsheaders für die Anfrage mit den Anmeldeinformationen.

  2. Hinzufügen der Anmeldeinformationen zum Hauptteil einer Anfrage zur ersten Anmeldung.

  3. Hinzufügen eines API-Schlüssels zu den Parametern einer Anfrage.

Der AL HttpClient und alle Zwischentypen, die zum Erstellen einer Anforderung erforderlich sind, unterstützen eine Methode, die den SecretText-Datentyp akzeptiert, sodass die Werte direkt an die AL-Laufzeit übergeben werden können, ohne für den Debugger offengelegt zu werden.

Der folgende Codeausschnitt zeigt, wie alle vorher genannten Szenarien mit diesen Methoden implementiert werden können.

procedure SendAuthenticatedRequestToApi(UriTemplate: Text; BearerToken: SecretText; KeyParameter: SecretText; SecretBody: SecretText)

  var
    Client: HttpClient;
    Headers: HttpHeaders;
    SecretHeader: SecretText;
    SecretUri: SecretText;
    RequestMessage: HttpRequestMessage;

  begin
    SecretHeader := SecretStrSubstNo('Bearer %1', BearerToken);
    RequestMessage.GetHeaders(Headers);

    // The header is added and remains hidden when debugging the headers
    // of the request message
    Headers.Add('Authorization', SecretHeader);

    // Headers.Contains('Authorization') - false
    // Headers.ContainsSecret('Authorization') - true
    // Headers.GetSecretValues must be used to get the values.
    // It is not possible to retrieve the header value as a plain text.

    SecretUri := SecretStrSubstNo(UriTemplate, KeyParameter);
    RequestMessage.SetSecretRequestUri(SecretUri);

    // RequestMessage.GetSecretRequestUri can be used to retrieve the request uri.
    // It cannot be retrieved by GetRequestUri as a plain text.

    RequestMessage.Content.WriteFrom(SecretBody);

    // RequestMessage.Content.ReadAs can only read back the body in a SecretText destination

    SendMessageAndHandleResponse(Client, RequestMessage);
end;

[NonDebuggable]
procedure SendMessageAndHandleResponse(Client: HttpClient; Request: HttpRequestMessage) CredentialFromResponse: SecretText

  var
    Response: HttpResponseMessage;

  begin
    Client.Send(Request, Response);
    Response.Content.ReadAs(CredentialFromResponse);
end;

Die Unwrap-Methode

Mit der Unwrap-Methode kann der Wert aus Kompatibilitätsgründen aus einem SecretText an ein Textziel extrahiert werden. Es ist nur beim Erstellen von Anwendungen für den Bereich OnPrem zulässig und seine Verwendung erzeugt eine Warnung, es sei denn, es wird in einem Verfahren mit dem NonDebuggable-Attribut aufgerufen. Es wird nicht empfohlen, diese Methode für andere Zwecke als die .NET-Interoperabilität zu nutzen.

Die SecretStrSubstNo-Methode

Die SecretStrSubstNo-Methode ermöglicht die Zusammenstellung verschiedener Werte vom Typ SecretText, ohne die entsprechenden Werte preiszugeben. Ihr Verhalten ist identisch mit dem der StrSubstNo-Methode für Textwerte, mit dem wichtigen Unterschied, dass ihre Parameter und ihr Rückgabewert dem Typ SecretText angehören.

Einige Beispiele werden im folgenden Ausschnitt demonstriert:

procedure SecretStrSubstNoExamples()

var
  First: SecretText;
  Second: SecretText;
  Result: SecretText;

begin
  // Concatenation
  Result := SecretStrSubstNo('%1%2', First, Second);

  // Build a header value
  Result := SecretStrSubstNo('Bearer %1', First);

  // Build a comma separated value list
  Result := SecretStrSubstNo('%1,%2', First, Second);

end;