Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Platform native support 1 - SDKs and OAuth #163

Open
radu-stefan-dt opened this issue Jan 4, 2024 · 1 comment
Open

Platform native support 1 - SDKs and OAuth #163

radu-stefan-dt opened this issue Jan 4, 2024 · 1 comment
Labels
feature New functionality

Comments

@radu-stefan-dt
Copy link
Collaborator

Summary
First part of achieving Dynatrace platform-native support.
Platform access is controlled via SDKs or APIs using OAuth authentication scheme. To truly support platform natively we must also have a client that can interact this way with the tenant.

Details
SDKs would be preferred here since they tend to offer an easier way to work with these operations but a while back it wasn't possible to use them outside of Apps. APIs would be the alternative.

Sub-tasks for this feature:

  • Write a platform client that uses OAuth for authentication
  • Duplicate the calls made using legacy APIs, and use platform SDKs or APIs to achieve the same results
  • Modify the "Add tenant" workflow to request details of an OAuth client instead of an API Token
  • Modify the environments tree view to use the platform client for platform tenants

Use Cases

  • Platform native enablement
  • Preparation for current SaaS sunset
  • Preparation for DQL appearing in extensions (1.280 onwards)

Challenges

  • SDKs were not supported outside of Apps last I checked
  • There may not be complete parity with legacy API functionality

Other notes
SSO Login workflow (like in AppToolkit) might be a solid alternative to OAuth clients. Worth investigating.

@radu-stefan-dt
Copy link
Collaborator Author

radu-stefan-dt commented Jan 9, 2024

Simple example on how one might use SDKs to create a platform client for this ticket.

import { QueryExecutionClient } from "@dynatrace-sdk/client-query";
import {
  PlatformHttpClient,
  RequestBodyTypes,
  HttpClientRequestOptions,
  HttpClientResponse,
  PlatformHttpClientResponse,
} from "@dynatrace-sdk/http-client";

const BASE_URL = "https://base.url.to.dynatrace";
const CLIENT_ID = "dt0s02.XXXXXXXX";
const CLIENT_SECRET = "dt0s02.XXXXXXXX.YYYYYYYYYYYYYYYYYYYYYYY";
const ACCOUNT_URN = "urn:dtaccount:aaaaaa-bbbb-cccc";
// Example for Sprint SSO. In reality, this will have to be dynamic.
const SSO_URL = "https://sso-sprint.dynatracelabs.com/sso/oauth2/token"

class SamplePlatformClient implements PlatformHttpClient {
  private readonly ssoUrl = SSO_URL;
  private readonly baseUrl: string;
  private readonly clientId: string;
  private readonly clientSecret: string;
  private readonly accountUrn: string;

  constructor(baseUrl: string, clientId: string, clientSecret: string, accountUrn: string) {
    this.baseUrl = baseUrl;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
    this.accountUrn = accountUrn;
  }

  private async getBearer() {
    const tokenResponse = await fetch(this.ssoUrl, {
      method: "POST",
      headers: {
        "content-type": "application/x-www-form-urlencoded",
      },
      body: new URLSearchParams({
        grant_type: "client_credentials",
        client_id: this.clientId,
        client_secret: this.clientSecret,
        scope: "storage:logs:read storage:buckets:read",
        resource: this.accountUrn,
      }).toString(),
    });
    const tokenData = (await tokenResponse.json()) as { access_token: string };
    return tokenData.access_token;
  }

  async send<T extends keyof RequestBodyTypes = "json">({
    url,
    method,
    headers,
    body,
    abortSignal,
  }: HttpClientRequestOptions<T>): Promise<HttpClientResponse> {
    const token = await this.getBearer();
    const requestConfig = {
      method: method,
      signal: abortSignal,
      headers: {
        ...headers,
        Authorization: `Bearer ${token}`,
      },
      body: JSON.stringify(body),
    };
    const response = await fetch(`${this.baseUrl}${url}`, requestConfig);
    return new PlatformHttpClientResponse(response);
  }
}

const httpClient = new SamplePlatformClient(BASE_URL, CLIENT_ID, CLIENT_SECRET, ACCOUNT_URN);
const dqlClient = new QueryExecutionClient(httpClient);

dqlClient
  .queryExecute({
    body: {
      query:
        "fetch logs, from: now() - 2h | summarize count=count(), by:{bin(timestamp, 10m), loglevel}",
      requestTimeoutMilliseconds: 30000,
    },
  })
  .then(res => {
    console.info(res);
  })
  .catch(err => {
    console.error(err);
  });

Things to consider:

  • Tokens don't need fetching on every request. Expiration details are available and should be checked.
  • SSO URL will differ between Dev, Sprint, and Prod
  • Token scopes will differ based on SDK used. Given token will be cached for a while there are 2 considerations:
    • Request all scopes ever needed = keep track of fewer tokens
    • Request only scopes needed by sdk = clearner / more precise but keep track of tokens per sdk/operation
  • Given we use Axios in the project, the whole token fetch/refetch should be handled as an interceptor on a custom axios instance

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature New functionality
Projects
None yet
Development

No branches or pull requests

1 participant