Skip to content

Commit

Permalink
feat(trace-otlp-grpc): configure security with env vars (#2827)
Browse files Browse the repository at this point in the history
* feat(trace-otlp-grpc): add insecure configs

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): add unit tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip add certificate and tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix security config and unit tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): add env and certificate tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip certificate tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix lint error

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip add additional security setting checks

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): update default url to http scheme

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip add tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip refactor function around insecure setting

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip update returned security setting for some use cases

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): update certificate and add tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip certificate tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): add diag tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): update default url

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): add changelog item

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): add grpc scheme check and update test

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): add grpc scheme test

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix metrics default url

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): update readme

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix lint

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix changelog and get security from env func

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): wip troubleshoot

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix readme

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): refactor code and fix lint

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): remove grpc scheme and grpc scheme tests

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): fix credentials for failing test afer main merge

Signed-off-by: Svetlana Brennan <[email protected]>

* feat(trace-otlp-grpc): move changelog to unreleased section

Signed-off-by: Svetlana Brennan <[email protected]>

* Use exact match for protocol check to avoid leaking cases like httpx

Co-authored-by: Chengzhong Wu <[email protected]>
Co-authored-by: Daniel Dyla <[email protected]>
  • Loading branch information
3 people authored May 17, 2022
1 parent 22085fc commit a9c59da
Show file tree
Hide file tree
Showing 11 changed files with 277 additions and 43 deletions.
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ All notable changes to experimental packages in this project will be documented

* feat(exporters): update proto version and use otlp-transformer #2929 @pichlermarc
* fix(sdk-metrics-base): misbehaving aggregation temporality selector tolerance #2958 @legendecas
* feat(trace-otlp-grpc): configure security with env vars #2827 @svetlanabrennan
* feat(sdk-metrics-base): async instruments callback timeout #2742 @legendecas

### :bug: (Bug Fix)
Expand Down
31 changes: 20 additions & 11 deletions experimental/packages/exporter-trace-otlp-grpc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/sdk
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const collectorOptions = {
// url is optional and can be omitted - default is localhost:4317
url: '<collector-hostname>:<port>',
// url is optional and can be omitted - default is http://localhost:4317
url: 'http://<collector-hostname>:<port>',
};

const provider = new BasicTracerProvider();
Expand All @@ -51,8 +51,8 @@ const { BasicTracerProvider, SimpleSpanProcessor } = require('@opentelemetry/sdk
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const collectorOptions = {
// url is optional and can be omitted - default is localhost:4317
url: '<collector-hostname>:<port>',
// url is optional and can be omitted - default is http://localhost:4317
url: 'http://<collector-hostname>:<port>',
credentials: grpc.credentials.createSsl(),
};

Expand Down Expand Up @@ -91,8 +91,8 @@ const metadata = new grpc.Metadata();
metadata.set('k', 'v');

const collectorOptions = {
// url is optional and can be omitted - default is localhost:4317
url: '<collector-hostname>:<port>',
// url is optional and can be omitted - default is http://localhost:4317
url: 'http://<collector-hostname>:<port>',
metadata, // // an optional grpc.Metadata object to be sent with each request
};

Expand Down Expand Up @@ -135,8 +135,8 @@ By default no compression will be used. To use compression, set it programmatica
const { CompressionAlgorithm } = require('@opentelemetry/exporter-trace-otlp-grpc');

const collectorOptions = {
// url is optional and can be omitted - default is localhost:4317
url: '<collector-hostname>:<port>',
// url is optional and can be omitted - default is http://localhost:4317
url: 'http://<collector-hostname>:<port>',
metadata, // // an optional grpc.Metadata object to be sent with each request
compression: CompressionAlgorithm.GZIP,
};
Expand All @@ -149,11 +149,20 @@ const exporter = new OTLPTraceExporter(collectorOptions);

| Environment variable | Description |
|----------------------|-------------|
| OTEL_EXPORTER_OTLP_TRACES_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP trace batch. Default is 10000. |
| OTEL_EXPORTER_OTLP_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP trace and metric batch. Default is 10000. |
| OTEL_EXPORTER_OTLP_TRACES_COMPRESSION | The compression type to use on OTLP trace requests. Options include gzip. By default no compression will be used. |
| OTEL_EXPORTER_OTLP_COMPRESSION | The compression type to use on OTLP trace, metric, and log requests. Options include gzip. By default no compression will be used. |
> The per-signal environment variables (`OTEL_EXPORTER_OTLP_TRACES_TIMEOUT`) takes precedence and non-per-signal environment variable (`OTEL_EXPORTER_OTLP_TIMEOUT`).
| OTEL_EXPORTER_OTLP_TRACES_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for trace requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. |
| OTEL_EXPORTER_OTLP_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for trace, metric and log requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. |
| OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP trace server's TLS credentials. By default the host platform's trusted root certificate is used.|
| OTEL_EXPORTER_OTLP_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP trace, metric, or log server's TLS credentials. By default the host platform's trusted root certificate is used. |
| OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP trace client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. |
| OTEL_EXPORTER_OTLP_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP trace, metric or log client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. |
| OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP trace server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. |
| OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP trace, metric and log server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. |
| OTEL_EXPORTER_OTLP_TRACES_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP trace batch. Default is 10000. |
| OTEL_EXPORTER_OTLP_TIMEOUT | The maximum waiting time, in milliseconds, allowed to send each OTLP trace and metric batch. Default is 10000. |

> Settings configured programmatically take precedence over environment variables. Per-signal environment variables take precedence over non-per-signal environment variables.
## Running opentelemetry-collector locally to see the traces

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,11 @@ import {
OTLPGRPCExporterConfigNode,
OTLPGRPCExporterNodeBase,
ServiceClientType,
validateAndNormalizeUrl
validateAndNormalizeUrl,
DEFAULT_COLLECTOR_URL
} from '@opentelemetry/otlp-grpc-exporter-base';
import { createExportTraceServiceRequest, IExportTraceServiceRequest } from '@opentelemetry/otlp-transformer';

const DEFAULT_COLLECTOR_URL = 'localhost:4317';

/**
* OTLP Trace Exporter for Node
*/
Expand Down Expand Up @@ -55,7 +54,7 @@ export class OTLPTraceExporter
? validateAndNormalizeUrl(getEnv().OTEL_EXPORTER_OTLP_TRACES_ENDPOINT)
: getEnv().OTEL_EXPORTER_OTLP_ENDPOINT.length > 0
? validateAndNormalizeUrl(getEnv().OTEL_EXPORTER_OTLP_ENDPOINT)
: DEFAULT_COLLECTOR_URL;
: validateAndNormalizeUrl(DEFAULT_COLLECTOR_URL);
}

getServiceClientType() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ const testCollectorExporter = (params: TestParams) =>
fs.readFileSync('./test/certs/client.key'),
fs.readFileSync('./test/certs/client.crt')
)
: undefined;
: grpc.credentials.createInsecure();
collectorExporter = new OTLPTraceExporter({
url: 'grpcs://' + address,
url: 'https://' + address,
credentials,
metadata: params.metadata,
});
Expand Down Expand Up @@ -207,7 +207,7 @@ const testCollectorExporter = (params: TestParams) =>
fs.readFileSync('./test/certs/client.key'),
fs.readFileSync('./test/certs/client.crt')
)
: undefined;
: grpc.credentials.createInsecure();

const collectorExporterWithTimeout = new OTLPTraceExporter({
url: 'grpcs://' + address,
Expand Down Expand Up @@ -236,9 +236,9 @@ const testCollectorExporter = (params: TestParams) =>
fs.readFileSync('./test/certs/client.key'),
fs.readFileSync('./test/certs/client.crt')
)
: undefined;
: grpc.credentials.createInsecure();
collectorExporter = new OTLPTraceExporter({
url: 'grpcs://' + address,
url: 'https://' + address,
credentials,
metadata: params.metadata,
compression: CompressionAlgorithm.GZIP,
Expand Down Expand Up @@ -286,11 +286,11 @@ const testCollectorExporter = (params: TestParams) =>
fs.readFileSync('./test/certs/client.key'),
fs.readFileSync('./test/certs/client.crt')
)
: undefined;
: grpc.credentials.createInsecure();

envSource.OTEL_EXPORTER_OTLP_COMPRESSION = 'gzip';
collectorExporter = new OTLPTraceExporter({
url: 'grpcs://' + address,
url: 'https://' + address,
credentials,
metadata: params.metadata,
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ The OTLPMetricsExporter in Node expects the URL to only be the hostname. It will
const { MeterProvider, PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics-base');
const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-grpc');
const collectorOptions = {
// url is optional and can be omitted - default is grpc://localhost:4317
url: 'grpc://<collector-hostname>:<port>',
// url is optional and can be omitted - default is http://localhost:4317
url: 'http://<collector-hostname>:<port>',
};

const exporter = new OTLPMetricExporter(collectorOptions);
Expand All @@ -48,6 +48,23 @@ const counter = meter.createCounter('metric_name');
counter.add(10, { 'key': 'value' });
```

## Environment Variable Configuration

| Environment variable | Description |
|----------------------|-------------|
| OTEL_EXPORTER_OTLP_METRICS_COMPRESSION | The compression type to use on OTLP metric requests. Options include gzip. By default no compression will be used. |
| OTEL_EXPORTER_OTLP_COMPRESSION | The compression type to use on OTLP trace, metric, and log requests. Options include gzip. By default no compression will be used. |
| OTEL_EXPORTER_OTLP_METRICS_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for metric requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. |
| OTEL_EXPORTER_OTLP_INSECURE | Whether to enable client transport security for the exporter's gRPC connection for trace, metric and log requests. This option only applies to OTLP/gRPC when an endpoint is provided without the http or https scheme. Options include true or false. By default insecure is false which creates a secure connection. |
| OTEL_EXPORTER_OTLP_METRICS_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP metric server's TLS credentials. By default the host platform's trusted root certificate is used.|
| OTEL_EXPORTER_OTLP_CERTIFICATE | The path to the file containing trusted root certificate to use when verifying an OTLP trace, metric, or log server's TLS credentials. By default the host platform's trusted root certificate is used. |
| OTEL_EXPORTER_OTLP_METRICS_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP metric client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. |
| OTEL_EXPORTER_OTLP_CLIENT_KEY | The path to the file containing private client key to use when verifying an OTLP trace, metric or log client's TLS credentials. Must provide a client certificate/chain when providing a private client key. By default no client key file is used. |
| OTEL_EXPORTER_OTLP_METRICS_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP metric server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. |
| OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE | The path to the file containing trusted client certificate/chain for clients private key to use when verifying an OTLP trace, metric and log server's TLS credentials. Must provide a private client key when providing a certificate/chain. By default no chain file is used. |

> Settings configured programmatically take precedence over environment variables. Per-signal environment variables take precedence over non-per-signal environment variables.
## Running opentelemetry-collector locally to see the metrics

1. Go to `examples/otlp-exporter-node`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,13 @@ import {
OTLPGRPCExporterConfigNode,
OTLPGRPCExporterNodeBase,
ServiceClientType,
validateAndNormalizeUrl
validateAndNormalizeUrl,
DEFAULT_COLLECTOR_URL
} from '@opentelemetry/otlp-grpc-exporter-base';
import { baggageUtils, getEnv } from '@opentelemetry/core';
import { Metadata } from '@grpc/grpc-js';
import { createExportMetricsServiceRequest, IExportMetricsServiceRequest } from '@opentelemetry/otlp-transformer';

const DEFAULT_COLLECTOR_URL = 'localhost:4317';


class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase<ResourceMetrics, IExportMetricsServiceRequest> {

constructor(config: OTLPGRPCExporterConfigNode & OTLPMetricExporterOptions= defaultOptions) {
Expand All @@ -59,7 +57,7 @@ class OTLPMetricExporterProxy extends OTLPGRPCExporterNodeBase<ResourceMetrics,
? validateAndNormalizeUrl(getEnv().OTEL_EXPORTER_OTLP_METRICS_ENDPOINT)
: getEnv().OTEL_EXPORTER_OTLP_ENDPOINT.length > 0
? validateAndNormalizeUrl(getEnv().OTEL_EXPORTER_OTLP_ENDPOINT)
: DEFAULT_COLLECTOR_URL;
: validateAndNormalizeUrl(DEFAULT_COLLECTOR_URL);
}

convert(metrics: ResourceMetrics[]): IExportMetricsServiceRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,9 @@ const testOTLPMetricExporter = (params: TestParams) =>
fs.readFileSync('./test/certs/client.key'),
fs.readFileSync('./test/certs/client.crt')
)
: undefined;
: grpc.credentials.createInsecure();
collectorExporter = new OTLPMetricExporter({
url: 'grpcs://' + address,
url: 'https://' + address,
credentials,
metadata: params.metadata,
temporalityPreference: AggregationTemporality.CUMULATIVE
Expand Down
2 changes: 1 addition & 1 deletion experimental/packages/otlp-grpc-exporter-base/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@

export * from './OTLPGRPCExporterNodeBase';
export { ServiceClientType, OTLPGRPCExporterConfigNode } from './types';
export { validateAndNormalizeUrl, GrpcCompressionAlgorithm } from './util';
export { DEFAULT_COLLECTOR_URL, validateAndNormalizeUrl, GrpcCompressionAlgorithm } from './util';
104 changes: 100 additions & 4 deletions experimental/packages/otlp-grpc-exporter-base/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,19 @@ import { getEnv, globalErrorHandler } from '@opentelemetry/core';
import * as path from 'path';
import { OTLPGRPCExporterNodeBase } from './OTLPGRPCExporterNodeBase';
import { URL } from 'url';
import * as fs from 'fs';
import { GRPCQueueItem, OTLPGRPCExporterConfigNode, ServiceClientType, } from './types';
import { CompressionAlgorithm, ExportServiceError, OTLPExporterError } from '@opentelemetry/otlp-exporter-base';

export const DEFAULT_COLLECTOR_URL = 'http://localhost:4317';

export function onInit<ExportItem, ServiceRequest>(
collector: OTLPGRPCExporterNodeBase<ExportItem, ServiceRequest>,
config: OTLPGRPCExporterConfigNode
): void {
collector.grpcQueue = [];
const credentials: grpc.ChannelCredentials =
config.credentials || grpc.credentials.createInsecure();

const credentials: grpc.ChannelCredentials = configureSecurity(config.credentials, collector.url);

const includeDirs = [path.resolve(__dirname, '..', 'protos')];

Expand Down Expand Up @@ -120,14 +123,107 @@ export function validateAndNormalizeUrl(url: string): string {
'URL path should not be set when using grpc, the path part of the URL will be ignored.'
);
}
if (target.protocol !== '' && !target.protocol?.match(/(http|grpc)s?/)) {
if (target.protocol !== '' && !target.protocol?.match(/^(http)s?:$/)) {

This comment has been minimized.

Copy link
@hmeerlo

hmeerlo Jun 1, 2022

Why are grpc URL's no longer supported?

diag.warn(
'URL protocol should be http(s):// or grpc(s)://. Using grpc://.'
'URL protocol should be http(s)://. Using http://.'
);
}
return target.host;
}

export function configureSecurity(credentials: grpc.ChannelCredentials | undefined, endpoint: string):
grpc.ChannelCredentials {

let insecure: boolean;

if (credentials) {
return credentials;
} else if (endpoint.startsWith('https://')) {
insecure = false;
} else if (endpoint.startsWith('http://') || endpoint === DEFAULT_COLLECTOR_URL) {
insecure = true;
} else {
insecure = getSecurityFromEnv();
}

if (insecure) {
return grpc.credentials.createInsecure();
} else {
return useSecureConnection();
}
}

function getSecurityFromEnv(): boolean {
const definedInsecure =
getEnv().OTEL_EXPORTER_OTLP_TRACES_INSECURE ||
getEnv().OTEL_EXPORTER_OTLP_INSECURE;

if (definedInsecure) {
return definedInsecure.toLowerCase() === 'true';
} else {
return false;
}
}

export function useSecureConnection(): grpc.ChannelCredentials {
const rootCertPath = retrieveRootCert();
const privateKeyPath = retrievePrivateKey();
const certChainPath = retrieveCertChain();

return grpc.credentials.createSsl(rootCertPath, privateKeyPath, certChainPath);
}

function retrieveRootCert(): Buffer | undefined {
const rootCertificate =
getEnv().OTEL_EXPORTER_OTLP_TRACES_CERTIFICATE ||
getEnv().OTEL_EXPORTER_OTLP_CERTIFICATE;

if (rootCertificate) {
try {
return fs.readFileSync(path.resolve(process.cwd(), rootCertificate));
} catch {
diag.warn('Failed to read root certificate file');
return undefined;
}
} else {
return undefined;
}
}

function retrievePrivateKey(): Buffer | undefined {
const clientKey =
getEnv().OTEL_EXPORTER_OTLP_TRACES_CLIENT_KEY ||
getEnv().OTEL_EXPORTER_OTLP_CLIENT_KEY;

if (clientKey) {
try {
return fs.readFileSync(path.resolve(process.cwd(), clientKey));
} catch {
diag.warn('Failed to read client certificate private key file');
return undefined;
}
} else {
return undefined;
}
}

function retrieveCertChain(): Buffer | undefined {
const clientChain =
getEnv().OTEL_EXPORTER_OTLP_TRACES_CLIENT_CERTIFICATE ||
getEnv().OTEL_EXPORTER_OTLP_CLIENT_CERTIFICATE;

if (clientChain) {
try {
return fs.readFileSync(path.resolve(process.cwd(), clientChain));
} catch {
diag.warn('Failed to read client certificate chain file');
return undefined;
}
} else {
return undefined;
}
}

function toGrpcCompression(compression: CompressionAlgorithm): GrpcCompressionAlgorithm {
if(compression === CompressionAlgorithm.NONE)
return GrpcCompressionAlgorithm.NONE;
Expand Down
Loading

0 comments on commit a9c59da

Please sign in to comment.