Protezione di valori sensibili

Completato

Con un numero crescente di integrazioni a sistemi esterni, non è raro lavorare in codice AL con segreti come credenziali e altri valori di testo sensibili. Considerare la necessità di proteggerli dalla rivelazione attraverso il debug. Quest'ultimo veniva spesso bloccato attraverso criteri di esposizione delle risorse, al costo di una facile risoluzione dei problemi.

Per supportare l'abilitazione del debug, proteggendo al contempo le credenziali e altri valori di testo sensibili dalla rivelazione, si introduce un nuovo tipo SecretText per le variabili. Inoltre, alcuni degli scenari comuni nell'app di sistema ricevono il supporto per il passaggio dei parametri SecretText per le credenziali. Ad esempio, i tipi HttpClient e Archiviazione isolata.

Il tipo di dati SecretText è progettato per proteggere i valori sensibili dall'esposizione tramite il debugger AL durante l'esecuzione del debug normale o snapshot. Il suo utilizzo è consigliato per applicazioni che necessitano di gestire qualsiasi tipo di credenziali come chiavi API, token di licenza personalizzati o simili.

Recupero

Quando una credenziale non è protetta dall'attributo NonDebuggable nell'ambito di una procedura o nella variabile in cui è contenuta, è vulnerabile all'esposizione in una sessione di debug o in uno snapshot per l'intero ciclo di vita nel codice AL. Il ciclo di vita può essere suddiviso in tre fasi distinte.

È possibile recuperare una credenziale in diversi modi:

  1. Si recupera una chiave API attraverso una chiamata tramite HttpClient AL e la si usa come autenticazione per chiamate successive.

  2. Si recupera un token tramite un componente aggiuntivo di controllo che implementa un'integrazione a un provider di autenticazione come OAUTH2.

  3. Uno scenario personalizzato definito dallo sviluppatore crea un token di autenticazione.

  4. Uno sviluppatore codifica erroneamente una credenziale nel codice a scopo di test e si dimentica di rimuoverla.

Qualsiasi valore di tipo Text o Code può essere assegnato a un valore SecretText. Se i token vengono recuperati e convertiti in un valore SecretText nell'ambito di una procedura di cui non è possibile eseguire il debug, sono protetti dal debugger per tutto il ciclo di vita. Inoltre, il compilatore AL assicura che non sia possibile assegnare direttamente una credenziale hardcoded a una destinazione di tipo SecretText.

[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;

Transito

Una credenziale transita attraverso il codice AL per raggiungere i punti in cui viene usata. Il transito include:

  • Assegnazione a variabili

  • Uso come parametro per chiamare una procedura/trigger

  • Valore restituito da una chiamata a funzione

Il tipo di dati SecretText assicura che il valore rimanga non sottoponibile a debug impedendo qualsiasi assegnazione da se stesso a qualsiasi tipo sottoponibile a debug. Questo vincolo include il tipo di dati Variant. Di conseguenza, l'attributo NonDebuggable è necessario solo durante il recupero e può essere omesso per il resto del ciclo di vita di una credenziale, poiché tutte le destinazioni intermedie sono protette automaticamente.

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;

Consumo

Si consuma la credenziale quando la si usa per eseguire un'operazione. Un esempio comune è la comunicazione con un servizio Web esterno tramite HttpClient AL in cui potrebbero essere necessari i seguenti passaggi:

  1. Creazione di un'intestazione di autenticazione per la richiesta con la credenziale.

  2. Aggiunta della credenziale al corpo di una richiesta per l'accesso iniziale.

  3. Aggiunta di una chiave API ai parametri di una richiesta.

HttpClient AL e tutti i tipi intermedi necessari per effettuare una richiesta supportano il metodo, che accetta il tipo di dati SecretText, in modo che i valori possano essere passati direttamente al runtime AL senza essere rivelati al debugger.

Il seguente frammento di codice dimostra come sia possibile implementare tramite questi metodi tutti gli scenari menzionati in precedenza.

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;

Metodo Unwrap

Il metodo Unwrap consente di estrarre il valore da un SecretText in una destinazione di testo per motivi di compatibilità. È consentito solo quando si creano applicazioni per l'ambito OnPrem e il relativo uso produce un avviso, a meno che non venga chiamato all'interno di una procedura con l'attributo NonDebuggable. Non è consigliabile usare questo metodo per qualsiasi altro uso, ad eccezione dell'interoperabilità .NET.

Metodo SecretStrSubstNo

Il metodo SecretStrSubstNo consente di comporre diversi valori di tipo SecretText senza rivelarne i valori. Il relativo comportamento è identico al metodo StrSubstNo sui valori Text con l'importante differenza che i relativi parametri e il valore restituito sono di tipo SecretText.

Alcuni esempi sono dimostrati nel seguente frammento di codice:

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;