diff --git a/scripts/docker-integration-tests/aggregator_tls/client.crt b/scripts/docker-integration-tests/aggregator_tls/client.crt new file mode 100644 index 0000000000..366d36e8b8 --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/client.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKwMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDE1MTQ0NjEyWhcNMzQwNDEzMTQ0NjEyWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0Q2xpZW50Q2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMXgv5tgZD5iBW3LfTC27jPHTLlsAw8ZlBFLmd7potdS3Hy512Xa +kKOaKRKH/0Yla4BKGNh0ZP+xege15CpTFqwtzDPCRkaRvfSxRW19MukeergRDqqI +tEwURHQ5gfub7XB9e5wuNmW2GGs/xgiWsxxm6UMidXkhA5a2//OmDSm2JdcoxJbk +1m1OUzXUuLKLzCIRAbJFFIM1RpvjWSonxpJ2lsNCVQ+LdItxE9mAI+Y+PaDonyzg +ehfAqkl+wxGRevaDFFJr1defxmnJhaYv40AcEpXOWeetZFlspEtrPF7b/3GifRdx +Mlwq27wn3mk4wF1wa12kRNKiz+HHjZrB7UECAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAKblZ+C4Pdv5grKiCQcfFt53SbIys8uavxmzJNn9nq1QVgBmR/hX8yG8ULeh5 +kKrccwe2WXCBWsIj9cvGCzR+MPMvuIXd3d58yLybmIUBDfJSBC0v5TaNWP2ZdJDG +fH38DfQTeE9XcjpRtCGYtYwReBptSlNiYQlvwkukTNz3mV+0BpvQu/9/R0BNCJAF +9VeLgUcG7T7HC+foCeU7h83Y0xH0OALe33ntcrMPHUclXqImCiC7dl8pb3H7o/xJ +jS3KZxfv6ioxpirvpEbX/EeF+H1HQZUZ8y3+J3K37jMgzNamd/xsaJxEfCq4SE+n +HmgeXgqoNjrM6J0jYAfx1MjbIA== +-----END CERTIFICATE----- diff --git a/scripts/docker-integration-tests/aggregator_tls/client.key b/scripts/docker-integration-tests/aggregator_tls/client.key new file mode 100644 index 0000000000..ff226a549e --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxeC/m2BkPmIFbct9MLbuM8dMuWwDDxmUEUuZ3umi11LcfLnX +ZdqQo5opEof/RiVrgEoY2HRk/7F6B7XkKlMWrC3MM8JGRpG99LFFbX0y6R56uBEO +qoi0TBREdDmB+5vtcH17nC42ZbYYaz/GCJazHGbpQyJ1eSEDlrb/86YNKbYl1yjE +luTWbU5TNdS4sovMIhEBskUUgzVGm+NZKifGknaWw0JVD4t0i3ET2YAj5j49oOif +LOB6F8CqSX7DEZF69oMUUmvV15/GacmFpi/jQBwSlc5Z561kWWykS2s8Xtv/caJ9 +F3EyXCrbvCfeaTjAXXBrXaRE0qLP4ceNmsHtQQIDAQABAoIBAAtyP8MuJT5SjzvV +rI0317mZCsAjFl42PZFujR0O6MOJ4IU6ftI+fWVpUnzm7wZQvdIy9xL2UK1Vx9hQ +Vj14hvQ4xfosf8IvRgy0gG6f8mT3xWOGYRHOTJemCHuso+85CtgZ+h+DsNPbX7g8 +fSkcBopbDZ07jg4OsdVzCoU+kr5Z1/VDe0O0rTDzGVP686tN9I+DmDYv2HlBUaSZ +SyNZrLJFziqFx4ATTIE3aTLd29pzTl09WiCcZSxhlS+ROnN2iW2xDLNgY9SdUo2a +3r8N+xhYDNQz2paxlsv2x/tCRJvrQDoX7S5QZw4P1pVr6wo6xC+BU2UlpVHifo4M +egyDbAECgYEA5Y9+JDOSQF2cvIV4xTMgVnkLig/ngYesS44xz8HYTRR5s1WW6oCX +3j+OosZBbJpXeBlTK31G9Z92+Om2/Cj2uDvff7EJdhZ5sAdMtUy5X7p9rGTEyi5F +ecZDBfmwHH0lFdwpNtlky1uoaUsVd4IS28WMHoHYDGW241IW8xOlmeECgYEA3Ksb +KR7JCbHx21wh5oPVn5BLpdag7D+M1jBFtjPrzLDNhXlsV9zkjn7xETvTXWU2vNlh +OkoRu4VFEAnsgtxh+gT0lo9ZG9IWi2qP99vPpB3AFYuujBu3GA02/iOo5pri/6R0 +ZNEEreAaaOcT2z6K9HG0KMQ6QpYgXSlzJW8sf2ECgYEAvn2fMA03bIAB4xJi0EkH +qZoScEOYWQ0rdRsOzJbPlc7K2nzImdmRrFRTWVFo0uUUdk2VjX4Mlx/3ir/uHzsi +2GieowhWkI4/9kloZv2+yegoBxkrj5ZsAov57AhxEoLqdkRWUvR8xp9Nleo/awcd +/Q7loh8fF9KDvAjPkHAaOCECgYA98bZNI7wxgYcwGbvWdrmX8iyaIBbKWsiRM7nN +/OM7cYIv7rbwLyzlp1LKkK2zsP7donP9pd82caHCb9a5oV3LjmqOfSz5d08m0cIa +RNUT79oE8lIMOJd8I/GFA8OdAGuqcaLOzjHvEVK4ke1sBTGCjwyQyQzFtljdbg5J +utyV4QKBgHkSBKwrgaump08MJFfK9uFo5sr5jy692XYWMlBVhc+piPwUsoztjI1O +8cvie0mMj6oofSoSyD4FUg9kFUCRMPC0kga/zlHyL1Y/uILX4XUFMl4P/gMbC7kw +0d6BdEsp5SU8Onc9eV/EW+q2Mz7/t5ZHkUrdDQYGgkIBqRZcXlka +-----END RSA PRIVATE KEY----- diff --git a/scripts/docker-integration-tests/aggregator_tls/docker-compose.yml b/scripts/docker-integration-tests/aggregator_tls/docker-compose.yml new file mode 100644 index 0000000000..3d7b9b1236 --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/docker-compose.yml @@ -0,0 +1,61 @@ +version: "3.5" +services: + dbnode01: + expose: + - "9000-9004" + - "2379-2380" + - "7201" + ports: + - "0.0.0.0:9000-9004:9000-9004" + - "0.0.0.0:2379-2380:2379-2380" + - "0.0.0.0:7201:7201" + networks: + - backend + image: "m3dbnode_integration:${REVISION}" + m3coordinator01: + expose: + - "7202" + - "7203" + - "7204" + ports: + - "0.0.0.0:7202:7202" + - "0.0.0.0:7203:7203" + - "0.0.0.0:7204:7204" + networks: + - backend + image: "m3coordinator_integration:${REVISION}" + volumes: + - "./m3coordinator.yml:/etc/m3coordinator/m3coordinator.yml" + - "./client.crt:/tmp/client.crt" + - "./client.key:/tmp/client.key" + - "./rootCA.crt:/tmp/rootCA.crt" + m3aggregator01: + expose: + - "6001" + - "6000" + ports: + - "127.0.0.1:6001:6001" + - "127.0.0.1:6000:6000" + networks: + - backend + environment: + - M3AGGREGATOR_HOST_ID=m3aggregator01 + image: "m3aggregator_integration:${REVISION}" + volumes: + - "./m3aggregator.yml:/etc/m3aggregator/m3aggregator.yml" + - "./server.crt:/tmp/server.crt" + - "./server.key:/tmp/server.key" + - "./rootCA.crt:/tmp/rootCA.crt" + m3aggregator02: + networks: + - backend + environment: + - M3AGGREGATOR_HOST_ID=m3aggregator02 + image: "m3aggregator_integration:${REVISION}" + volumes: + - "./m3aggregator.yml:/etc/m3aggregator/m3aggregator.yml" + - "./server.crt:/tmp/server.crt" + - "./server.key:/tmp/server.key" + - "./rootCA.crt:/tmp/rootCA.crt" +networks: + backend: diff --git a/scripts/docker-integration-tests/aggregator_tls/m3aggregator.yml b/scripts/docker-integration-tests/aggregator_tls/m3aggregator.yml new file mode 100644 index 0000000000..d71e1bb251 --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/m3aggregator.yml @@ -0,0 +1,262 @@ +logging: + level: info + +metrics: + scope: + prefix: m3aggregator + prometheus: + onError: none + handlerPath: /metrics + sanitization: prometheus + samplingRate: 1.0 + extended: none + +http: + listenAddress: 0.0.0.0:6001 + readTimeout: 60s + writeTimeout: 60s + +rawtcp: + listenAddress: 0.0.0.0:6000 + keepAliveEnabled: true + keepAlivePeriod: 1m + tls: + mode: enforced + mTLSEnabled: true + certFile: /tmp/server.crt + keyFile: /tmp/server.key + clientCAFile: /tmp/rootCA.crt + retry: + initialBackoff: 5ms + backoffFactor: 2.0 + maxBackoff: 1s + forever: true + jitter: true + readBufferSize: 65536 + protobufIterator: + initBufferSize: 1440 + maxMessageSize: 50000000 # max message size is 50MB + bytesPool: + buckets: + - count: 1024 + capacity: 2048 + - count: 512 + capacity: 4096 + - count: 256 + capacity: 8192 + - count: 128 + capacity: 16384 + - count: 64 + capacity: 32768 + - count: 32 + capacity: 65536 + watermark: + low: 0.001 + high: 0.002 + +kvClient: + etcd: + env: override_test_env + zone: embedded + service: m3aggregator + cacheDir: /var/lib/m3kv + etcdClusters: + - zone: embedded + endpoints: + - dbnode01:2379 + +runtimeOptions: + kvConfig: + environment: override_test_env + zone: embedded + writeValuesPerMetricLimitPerSecondKey: write-values-per-metric-limit-per-second + writeValuesPerMetricLimitPerSecond: 0 + writeNewMetricLimitClusterPerSecondKey: write-new-metric-limit-cluster-per-second + writeNewMetricLimitClusterPerSecond: 0 + writeNewMetricNoLimitWarmupDuration: 0 + +aggregator: + hostID: + resolver: environment + envVarName: M3AGGREGATOR_HOST_ID + instanceID: + type: host_id + metricPrefix: "" + counterPrefix: "" + timerPrefix: "" + gaugePrefix: "" + aggregationTypes: + counterTransformFnType: empty + timerTransformFnType: suffix + gaugeTransformFnType: empty + aggregationTypesPool: + size: 1024 + quantilesPool: + buckets: + - count: 256 + capacity: 4 + - count: 128 + capacity: 8 + stream: + eps: 0.001 + capacity: 32 + streamPool: + size: 4096 + samplePool: + size: 4096 + floatsPool: + buckets: + - count: 4096 + capacity: 16 + - count: 2048 + capacity: 32 + - count: 1024 + capacity: 64 + client: + placementKV: + namespace: /placement + zone: embedded + environment: override_test_env + placementWatcher: + key: m3aggregator + initWatchTimeout: 15s + hashType: murmur32 + shardCutoffLingerDuration: 1m + encoder: + initBufferSize: 100 + maxMessageSize: 50000000 + bytesPool: + buckets: + - capacity: 16 + count: 10 + - capacity: 32 + count: 20 + watermark: + low: 0.001 + high: 0.01 + maxTimerBatchSize: 140 + queueSize: 1000 + queueDropType: oldest + connection: + connectionTimeout: 1s + connectionKeepAlive: true + writeTimeout: 1s + initReconnectThreshold: 2 + maxReconnectThreshold: 5000 + reconnectThresholdMultiplier: 2 + maxReconnectDuration: 1m + placementManager: + kvConfig: + namespace: /placement + environment: override_test_env + zone: embedded + placementWatcher: + key: m3aggregator + initWatchTimeout: 10s + hashType: murmur32 + bufferDurationBeforeShardCutover: 10m + bufferDurationAfterShardCutoff: 10m + resignTimeout: 1m + flushTimesManager: + kvConfig: + environment: override_test_env + zone: embedded + flushTimesKeyFmt: shardset/%d/flush + flushTimesPersistRetrier: + initialBackoff: 100ms + backoffFactor: 2.0 + maxBackoff: 2s + maxRetries: 3 + electionManager: + election: + leaderTimeout: 10s + resignTimeout: 10s + ttlSeconds: 10 + serviceID: + name: m3aggregator + environment: override_test_env + zone: embedded + electionKeyFmt: shardset/%d/lock + campaignRetrier: + initialBackoff: 100ms + backoffFactor: 2.0 + maxBackoff: 2s + forever: true + jitter: true + changeRetrier: + initialBackoff: 100ms + backoffFactor: 2.0 + maxBackoff: 5s + forever: true + jitter: true + resignRetrier: + initialBackoff: 100ms + backoffFactor: 2.0 + maxBackoff: 5s + forever: true + jitter: true + campaignStateCheckInterval: 1s + shardCutoffCheckOffset: 30s + flushManager: + checkEvery: 1s + jitterEnabled: true + maxJitters: + - flushInterval: 5s + maxJitterPercent: 1.0 + - flushInterval: 10s + maxJitterPercent: 0.5 + - flushInterval: 1m + maxJitterPercent: 0.5 + - flushInterval: 10m + maxJitterPercent: 0.5 + - flushInterval: 1h + maxJitterPercent: 0.25 + numWorkersPerCPU: 0.5 + maxBufferSize: 5m + forcedFlushWindowSize: 10s + flush: + handlers: + - dynamicBackend: + name: m3msg + hashType: murmur32 + producer: + buffer: + maxBufferSize: 1000000000 # max buffer before m3msg start dropping data. + writer: + topicName: aggregated_metrics + topicServiceOverride: + zone: embedded + environment: override_test_env + messageRetry: + initialBackoff: 1m + maxBackoff: 2m + messageQueueNewWritesScanInterval: 1s + ackErrorRetry: + initialBackoff: 2s + maxBackoff: 10s + connection: + dialTimeout: 5s + writeTimeout: 5s + retry: + initialBackoff: 1s + maxBackoff: 10s + flushInterval: 1s + writeBufferSize: 16384 + readBufferSize: 256 + forwarding: + maxSingleDelay: 5s + entryTTL: 6h + entryCheckInterval: 10m + maxTimerBatchSizePerWrite: 140 + defaultStoragePolicies: + - 10s:2d + maxNumCachedSourceSets: 2 + discardNaNAggregatedValues: true + entryPool: + size: 4096 + counterElemPool: + size: 4096 + timerElemPool: + size: 4096 + gaugeElemPool: + size: 4096 diff --git a/scripts/docker-integration-tests/aggregator_tls/m3coordinator.yml b/scripts/docker-integration-tests/aggregator_tls/m3coordinator.yml new file mode 100644 index 0000000000..7df48ab953 --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/m3coordinator.yml @@ -0,0 +1,78 @@ +listenAddress: 0.0.0.0:7202 + +carbon: + ingester: + listenAddress: "0.0.0.0:7204" + rules: + - pattern: .* + aggregation: + type: mean + policies: + - resolution: 10s + retention: 6h + +clusters: + - client: + config: + service: + env: default_env + zone: embedded + service: m3db + cacheDir: /var/lib/m3kv + etcdClusters: + - zone: embedded + endpoints: + - dbnode01:2379 + +downsample: + remoteAggregator: + client: + placementKV: + namespace: /placement + environment: override_test_env + placementWatcher: + key: m3aggregator + initWatchTimeout: 10s + hashType: murmur32 + shardCutoffLingerDuration: 1m + forceFlushEvery: 1s + flushWorkerCount: 4 + maxTimerBatchSize: 1120 + queueSize: 100 + queueDropType: oldest + encoder: + initBufferSize: 2048 + maxMessageSize: 10485760 + bytesPool: + buckets: + - capacity: 2048 + count: 4096 + - capacity: 4096 + count: 4096 + watermark: + low: 0.7 + high: 1.0 + connection: + writeTimeout: 250ms + tls: + enabled: true + insecureSkipVerify: true + caFile: /tmp/rootCA.crt + certFile: /tmp/client.crt + keyFile: /tmp/client.key + +ingest: + ingester: + workerPoolSize: 100 + opPool: + size: 10000 + retry: + maxRetries: 3 + jitter: true + logSampleRate: 0.01 + m3msg: + server: + listenAddress: "0.0.0.0:7507" + retry: + maxBackoff: 10s + jitter: true diff --git a/scripts/docker-integration-tests/aggregator_tls/rootCA.crt b/scripts/docker-integration-tests/aggregator_tls/rootCA.crt new file mode 100644 index 0000000000..5d92901e85 --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/rootCA.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIUUwZEZu6XUKW03HPLHGDlt/d/BeUwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKVGVzdFJvb3RDQTAe +Fw0yNDA0MDQxMTAwMzhaFw0zNDAxMDIxMTAwMzhaMFoxCzAJBgNVBAYTAk5MMRMw +EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDRwn7QLv6zup98kB9VJ1HvHQQMZxt2tqhpFGEc0bXiGrmhFKjV +XZ264ALnUJHQgM8rkRe+dTXg8455FPTMoT+O/BC44pt8CaY1C5kQzVkLaMwaVVxV +vsBfdZ9LOKJQmUXIf1qV/UK3P55Oc3TvwSQW3tU0eMMEOnuNdA4lDdlQnExb7tIJ +4sl0i2w6rB7CoS/0gx2eUPLhPK+vcwCM3d81Odb3ULVznyJ5ZUd5mYY85XXVFQdW +QSyaEE0ekzbmXQzjiqNAt67WEmbadoKdeg8jPG5ka8qBZ21ZGjCHQ8AbzpOn+eDk +n50zwxncpSYVxTilV70PbFSY33PdmdS6XjEnAgMBAAGjUzBRMB0GA1UdDgQWBBSo +kzZddlleTXZYQsBc8efmsZj3WTAfBgNVHSMEGDAWgBSokzZddlleTXZYQsBc8efm +sZj3WTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQACb1ZT3WPK +XdO6jYbZuMN5BBET+mNKQOYrdehY5oiLgr5sDwTwfZvf+q6ic7MpDRBOWMSQuYwp +aUxK7qfw4nSSZb4zDPpzB6Tauh7M5TTPYQwzesn9/JaUBO4vCTwOFGuWxEbXe6PF +VuQOyGQCR9a7juNgoDLiRpWi+xXHz1kMNSeZqV8bn7eo/SIziuSW1JScse91Brt1 +Db9L8iSJpKLDLtwv8fU4/NSORc/672uy4OLxNppsTthh1rk94KbC/FYllC6e8N8n +0iffEn4xE/CECYmAhvplXXS20hCTGbs7tPJ3DGsRfHOyI9RXiayNZuq9GIYKv6eR ++h7NYwXua7oP +-----END CERTIFICATE----- diff --git a/scripts/docker-integration-tests/aggregator_tls/server.crt b/scripts/docker-integration-tests/aggregator_tls/server.crt new file mode 100644 index 0000000000..3618a40052 --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKvMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDA0MTE0ODIwWhcNMzEwNzA3MTE0ODIwWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0U2VydmVyQ2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOoGyqgrsBCufd9UNt6wr9UJzPaVqdrr/Zble2UPMVZOynAT69W5 +gMdNvrIDU6l1EnRkLyxmP7+beYZvWyU+kN9LyP0pdcgSQdtyhbr1CkqMxhM1MA2B +OFvcEFN2rybaqcLDlq2WUQkmyuVQt7/weLU9jC596PtxUL1e0+wqIItISWpcmemf +cwH9TVlD8Yh6emf87v27afxc0bZCauPsrEPvOC00oU4epXnA7TeDs4RtfqVAuVHX +oEG3oNZ3NHXIIgSht3rNAEfOxBX+7KGvW4hW9ci6W758ECGzHFJThhzO3+w+0YPP +29m+v4CVRaXAGxkVRPiAUDpU8RSPteywTyMCAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAuQME+WSPw1KKK1jgh1wuiyM5hou1BeagfwOipM3CEblTiHfoERsd2f7Oi6e2 +zPACrvKjVP6bHKjZPEztFHGXDuLdBqK97Vd/o4IFl6p/8EuBeWV85o4Rk1PMhqBI +0jSxsXXhBLgSTkUBgSllwgAbAaxm+L3wBJiQOhh/wEdfNSJ/GeL5477J56Q12/hA +rRCghYRAVVpr44NfG/PbuuMklQHK2l+wkJFBnx+zAAtZF/nb5yCVSy8z/p2TT72a +STfegXYJtE3p4X2+78xajrI7x/PWhNlzJRn3txLW6461vYJIB3ZDVUA2LZtmi3nd +9BUuJVSvsoVwlvrUBF0S1P0vWg== +-----END CERTIFICATE----- diff --git a/scripts/docker-integration-tests/aggregator_tls/server.key b/scripts/docker-integration-tests/aggregator_tls/server.key new file mode 100644 index 0000000000..6b6e1cd4cc --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6gbKqCuwEK5931Q23rCv1QnM9pWp2uv9luV7ZQ8xVk7KcBPr +1bmAx02+sgNTqXUSdGQvLGY/v5t5hm9bJT6Q30vI/Sl1yBJB23KFuvUKSozGEzUw +DYE4W9wQU3avJtqpwsOWrZZRCSbK5VC3v/B4tT2MLn3o+3FQvV7T7Cogi0hJalyZ +6Z9zAf1NWUPxiHp6Z/zu/btp/FzRtkJq4+ysQ+84LTShTh6lecDtN4OzhG1+pUC5 +UdegQbeg1nc0dcgiBKG3es0AR87EFf7soa9biFb1yLpbvnwQIbMcUlOGHM7f7D7R +g8/b2b6/gJVFpcAbGRVE+IBQOlTxFI+17LBPIwIDAQABAoIBAG7rZSYr/rkJWxN8 +wLzvlYctq/27ldPzQjBA+Ck/+CxmD8DrUD3uPEE6cgXBWefZWzPbQBiVJeXU9NZo +9RREATo0m0CSZAa90IUIVKX1+ji3XDJCZVIhSusO9JX6jK2nugpb1ZDUy/fAcS4b +AdxxR2TvlP98Ie77GD+JMHygwugAJPQ4AFZQjO2F/2k3crVt9Frt1VSvf72nkM/C +sDJy4kv+lDZsbeveNPzJo1honiKdCDWpg4duLiLkkG4YvftWHGzpXHoOWnuwnrT5 +jrQTaG10d1jMpnxOwDrxRsFYWtz4kbJm2VtRsgWRIy4A4Vv8VKfFJ0cSu0CChjTE +7yVrExECgYEA9orYMAuuu/c0HGVcXOsA9x6XHtBXr5wK1fI973sGUrtkshTlBgjG +Bc7H+AhVBVdqr2ipm9pFZYSV1OYB2+3AgMQjhoZANR9jr5bmRjJgWuBi5umrFKa4 +n0IQbkwSzjxKvkhpJK25BY2kpjh7/128Q80PMkKnm0LR2gyUWvLFuUUCgYEA8wEJ +PyxHBu93NnbwTP0SUEOYFa654ctiVLbicdcUsbo/Qo4gl/0AIpGmMdeKqx3FWn9I +kCH1xlvb7hp7FNFhDZIiw6CT8V2RQHXdEO9/S0jjOOBg29hxARIgoU+PjjaQG/H+ +SqlDR/ijXUghlllwtP6jCJqy29+Ukpx8WnIkiUcCgYEA4DNXcinNlyWw//m1StJz +KEdrShxRAWO3/uOyKywHjgjYA5I4y4oC+pkXBJofJyJd5HIbe5286hruXpNkMWo+ +W1sCq30+upB3COEDO+bn9PyFNFRevgL3deyQIJ2RpHbzyaLz6uUIkuJCTt6i9JF7 +eXTrUtuGrAzR5ab26tZHtLkCgYBZtW42diAafVjjMHFx2pNiMjH4/zH8G1tHruRV +5nLrUbh8139M3Mep+18BU3MLzMfIquic3Qygl9TPR9NcUhekLOjMk4FKP6KZy2k7 +EU9K+bN4kczwHC1LmeeV5Ys3kwkMZ//ajDYcYbvdef18RbSH/OEvEf0pkpaEsWRu +92hpjQKBgH6Y3dW0bQIhXikjboD00vPn0zPuh35I9OfrPHMqDJiz+xom7Sz9tlGk +NfFll5PTf0gZtU7160uzhCp/OqXT3IdVgVt/G5ey13oYP7pCb8MWiuyNW0yUgtTp +GM5GUnEW5zJ1t2qJOxwoq/FA7D5C7EGRM/QpoMD0tsJoJKYyWkJO +-----END RSA PRIVATE KEY----- diff --git a/scripts/docker-integration-tests/aggregator_tls/test.sh b/scripts/docker-integration-tests/aggregator_tls/test.sh new file mode 100755 index 0000000000..b18c5fdf37 --- /dev/null +++ b/scripts/docker-integration-tests/aggregator_tls/test.sh @@ -0,0 +1,125 @@ +#!/usr/bin/env bash + +set -xe + +source "$M3_PATH"/scripts/docker-integration-tests/common.sh +REVISION=$(git rev-parse HEAD) +COMPOSE_FILE="$M3_PATH"/scripts/docker-integration-tests/aggregator_tls/docker-compose.yml +export REVISION + +echo "Run m3dbnode" +docker-compose -f ${COMPOSE_FILE} up -d dbnode01 + +# Stop containers on exit +METRIC_EMIT_PID="-1" +function defer { + docker-compose -f ${COMPOSE_FILE} down || echo "unable to shutdown containers" # CI fails to stop all containers sometimes + if [ "$METRIC_EMIT_PID" != "-1" ]; then + echo "Kill metric emit process" + kill $METRIC_EMIT_PID + fi +} +trap defer EXIT + +echo "Setup DB node" +AGG_RESOLUTION=10s AGG_RETENTION=6h setup_single_m3db_node + +echo "Initializing aggregator topology" +curl -vvvsSf -X POST -H "Cluster-Environment-Name: override_test_env" localhost:7201/api/v1/services/m3aggregator/placement/init -d '{ + "num_shards": 64, + "replication_factor": 2, + "instances": [ + { + "id": "m3aggregator01", + "isolation_group": "availability-zone-a", + "zone": "embedded", + "weight": 100, + "endpoint": "m3aggregator01:6000", + "hostname": "m3aggregator01", + "port": 6000 + }, + { + "id": "m3aggregator02", + "isolation_group": "availability-zone-b", + "zone": "embedded", + "weight": 100, + "endpoint": "m3aggregator02:6000", + "hostname": "m3aggregator02", + "port": 6000 + } + ] +}' + +echo "Initializing m3msg topic for m3coordinator ingestion from m3aggregators" +curl -vvvsSf -X POST -H "Cluster-Environment-Name: override_test_env" localhost:7201/api/v1/topic/init -d '{ + "numberOfShards": 64 +}' + +echo "Initializing m3coordinator topology" +curl -vvvsSf -X POST localhost:7201/api/v1/services/m3coordinator/placement/init -d '{ + "instances": [ + { + "id": "m3coordinator01", + "zone": "embedded", + "endpoint": "m3coordinator01:7507", + "hostname": "m3coordinator01", + "port": 7507 + } + ] +}' +echo "Done initializing m3coordinator topology" + +echo "Validating m3coordinator topology" +[ "$(curl -sSf localhost:7201/api/v1/services/m3coordinator/placement | jq .placement.instances.m3coordinator01.id)" == '"m3coordinator01"' ] +echo "Done validating topology" + +# Do this after placement for m3coordinator is created. +echo "Adding m3coordinator as a consumer to the aggregator topic" +curl -vvvsSf -X POST -H "Cluster-Environment-Name: override_test_env" localhost:7201/api/v1/topic -d '{ + "consumerService": { + "serviceId": { + "name": "m3coordinator", + "environment": "default_env", + "zone": "embedded" + }, + "consumptionType": "SHARED", + "messageTtlNanos": "600000000000" + } +}' # msgs will be discarded after 600000000000ns = 10mins + +echo "Running m3coordinator container" +echo "> port 7202 is coordinator API" +echo "> port 7203 is coordinator metrics" +echo "> port 7204 is coordinator graphite ingest" +echo "> port 7507 is coordinator m3msg ingest from aggregator ingest" +docker-compose -f ${COMPOSE_FILE} up -d m3coordinator01 +COORDINATOR_API="localhost:7202" + +echo "Running m3aggregator containers" +docker-compose -f ${COMPOSE_FILE} up -d m3aggregator01 +docker-compose -f ${COMPOSE_FILE} up -d m3aggregator02 + +echo "Verifying aggregation with remote aggregators" + +function read_carbon { + target=$1 + expected_val=$2 + end=$(date +%s) + start=$(($end-1000)) + RESPONSE=$(curl -sSfg "http://${COORDINATOR_API}/api/v1/graphite/render?target=$target&from=$start&until=$end") + test "$(echo "$RESPONSE" | jq ".[0].datapoints | .[][0] | select(. != null)" | tail -n 1)" = "$expected_val" + return $? +} + +# Send metric values 40 and 44 every second +echo "Sending unaggregated carbon metrics to m3coordinator" +bash -c 'while true; do t=$(date +%s); echo "foo.bar.baz 40 $t" | nc -q 0 0.0.0.0 7204; echo "foo.bar.baz 44 $t" | nc -q 0 0.0.0.0 7204; sleep 1; done' & + +# Track PID to kill on exit +METRIC_EMIT_PID="$!" + +# Read back the averaged averaged metric, we configured graphite +# aggregation policy to average each tile and we are emitting +# values 40 and 44 to get an average of 42 each tile +echo "Read back aggregated averaged metric" +ATTEMPTS=10 TIMEOUT=1 retry_with_backoff read_carbon foo.bar.* 42 diff --git a/scripts/docker-integration-tests/run.sh b/scripts/docker-integration-tests/run.sh index bf1cb9bf8f..14b612d31e 100755 --- a/scripts/docker-integration-tests/run.sh +++ b/scripts/docker-integration-tests/run.sh @@ -8,6 +8,7 @@ TESTS=( scripts/docker-integration-tests/carbon/test.sh scripts/docker-integration-tests/aggregator/test.sh scripts/docker-integration-tests/aggregator_legacy/test.sh + scripts/docker-integration-tests/aggregator_tls/test.sh scripts/docker-integration-tests/query_fanout/test.sh scripts/docker-integration-tests/repair/test.sh scripts/docker-integration-tests/replication/test.sh diff --git a/src/aggregator/client/config.go b/src/aggregator/client/config.go index a3779df345..7e5ead724c 100644 --- a/src/aggregator/client/config.go +++ b/src/aggregator/client/config.go @@ -39,6 +39,7 @@ import ( "github.com/m3db/m3/src/x/net" "github.com/m3db/m3/src/x/pool" "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" ) var errNoM3MsgOptions = errors.New("m3msg aggregator client: missing m3msg options") @@ -208,6 +209,28 @@ func (c *Configuration) NewClientOptions( return opts, nil } +// TLSConfiguration contains the TLS configuration +type TLSConfiguration struct { + Enabled bool `yaml:"enabled"` + InsecureSkipVerify bool `yaml:"insecureSkipVerify"` + ServerName string `yaml:"serverName"` + CAFile string `yaml:"caFile"` + CertFile string `yaml:"certFile"` + KeyFile string `yaml:"keyFile"` + CertificatesTTL time.Duration `yaml:"certificatesTTL"` +} + +// NewTLSOptions creates new TLS options +func (c *TLSConfiguration) NewTLSOptions() xtls.Options { + return xtls.NewOptions(). + SetClientEnabled(c.Enabled). + SetInsecureSkipVerify(c.InsecureSkipVerify). + SetServerName(c.ServerName). + SetCAFile(c.CAFile). + SetCertFile(c.CertFile). + SetKeyFile(c.KeyFile) +} + // ConnectionConfiguration contains the connection configuration. type ConnectionConfiguration struct { ConnectionTimeout time.Duration `yaml:"connectionTimeout"` @@ -218,6 +241,7 @@ type ConnectionConfiguration struct { ReconnectThresholdMultiplier int `yaml:"reconnectThresholdMultiplier"` MaxReconnectDuration *time.Duration `yaml:"maxReconnectDuration"` WriteRetries *retry.Configuration `yaml:"writeRetries"` + TLS *TLSConfiguration `yaml:"tls"` ContextDialerFn net.ContextDialerFn `yaml:"-"` } @@ -249,6 +273,9 @@ func (c *ConnectionConfiguration) NewConnectionOptions(scope tally.Scope) Connec retryOpts := c.WriteRetries.NewOptions(scope) opts = opts.SetWriteRetryOptions(retryOpts) } + if c.TLS != nil { + opts = opts.SetTLSOptions(c.TLS.NewTLSOptions()) + } if c.ContextDialerFn != nil { opts = opts.SetContextDialer(c.ContextDialerFn) } diff --git a/src/aggregator/client/config_test.go b/src/aggregator/client/config_test.go index 7ec1276b7a..df78a68062 100644 --- a/src/aggregator/client/config_test.go +++ b/src/aggregator/client/config_test.go @@ -80,6 +80,13 @@ connection: maxBackoff: 1s maxRetries: 2 jitter: true + tls: + enabled: true + insecureSkipVerify: true + serverName: TestServer + caFile: /tmp/ca + certFile: /tmp/cert + keyFile: /tmp/key ` func TestConfigUnmarshal(t *testing.T) { @@ -120,6 +127,12 @@ func TestConfigUnmarshal(t *testing.T) { require.Equal(t, time.Second, cfg.Connection.WriteRetries.MaxBackoff) require.Equal(t, 2, cfg.Connection.WriteRetries.MaxRetries) require.Equal(t, true, *cfg.Connection.WriteRetries.Jitter) + require.True(t, cfg.Connection.TLS.Enabled) + require.True(t, cfg.Connection.TLS.InsecureSkipVerify) + require.Equal(t, "TestServer", cfg.Connection.TLS.ServerName) + require.Equal(t, "/tmp/ca", cfg.Connection.TLS.CAFile) + require.Equal(t, "/tmp/cert", cfg.Connection.TLS.CertFile) + require.Equal(t, "/tmp/key", cfg.Connection.TLS.KeyFile) require.Nil(t, cfg.Connection.WriteRetries.Forever) } @@ -171,4 +184,10 @@ func TestNewClientOptions(t *testing.T) { require.Equal(t, 2, opts.ConnectionOptions().WriteRetryOptions().MaxRetries()) require.Equal(t, true, opts.ConnectionOptions().WriteRetryOptions().Jitter()) require.Equal(t, false, opts.ConnectionOptions().WriteRetryOptions().Forever()) + require.True(t, opts.ConnectionOptions().TLSOptions().ClientEnabled()) + require.True(t, opts.ConnectionOptions().TLSOptions().InsecureSkipVerify()) + require.Equal(t, "TestServer", opts.ConnectionOptions().TLSOptions().ServerName()) + require.Equal(t, "/tmp/ca", opts.ConnectionOptions().TLSOptions().CAFile()) + require.Equal(t, "/tmp/cert", opts.ConnectionOptions().TLSOptions().CertFile()) + require.Equal(t, "/tmp/key", opts.ConnectionOptions().TLSOptions().KeyFile()) } diff --git a/src/aggregator/client/conn.go b/src/aggregator/client/conn.go index 8fa1e4b7e5..6f31b27c75 100644 --- a/src/aggregator/client/conn.go +++ b/src/aggregator/client/conn.go @@ -22,6 +22,7 @@ package client import ( "context" + "crypto/tls" "errors" "math/rand" "net" @@ -34,6 +35,7 @@ import ( xio "github.com/m3db/m3/src/x/io" xnet "github.com/m3db/m3/src/x/net" "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" ) const ( @@ -77,6 +79,7 @@ type connection struct { mtx sync.Mutex keepAlive bool dialer xnet.ContextDialerFn + tlsConfigManager xtls.ConfigManager } // newConnection creates a new connection. @@ -100,7 +103,8 @@ func newConnection(addr string, opts ConnectionOptions) *connection { uninitWriter, xio.ResettableWriterOptions{WriteBufferSize: 0}, ), - metrics: newConnectionMetrics(opts.InstrumentOptions().MetricsScope()), + metrics: newConnectionMetrics(opts.InstrumentOptions().MetricsScope()), + tlsConfigManager: xtls.NewConfigManager(opts.TLSOptions(), opts.InstrumentOptions()), } c.connectWithLockFn = c.connectWithLock c.writeWithLockFn = c.writeWithLock @@ -152,6 +156,14 @@ func (c *connection) Close() { c.mtx.Unlock() } +func (c *connection) upgradeToTLS(conn net.Conn) (net.Conn, error) { + tlsConfig, err := c.tlsConfigManager.TLSConfig() + if err != nil { + return nil, err + } + return tls.Client(conn, tlsConfig), nil +} + // writeAttemptWithLock attempts to establish a new connection and writes raw bytes // to the connection while holding the write lock. // If the write succeeds, c.conn is guaranteed to be a valid connection on return. @@ -192,6 +204,16 @@ func (c *connection) connectWithLock() error { } } + if c.tlsConfigManager.ClientEnabled() { + securedConn, err := c.upgradeToTLS(conn) + if err != nil { + c.metrics.connectError.Inc(1) + conn.Close() // nolint: errcheck + return err + } + conn = securedConn + } + if c.conn != nil { c.conn.Close() // nolint: errcheck } diff --git a/src/aggregator/client/conn_options.go b/src/aggregator/client/conn_options.go index 98e7073f00..db6f1fc807 100644 --- a/src/aggregator/client/conn_options.go +++ b/src/aggregator/client/conn_options.go @@ -28,6 +28,7 @@ import ( xio "github.com/m3db/m3/src/x/io" xnet "github.com/m3db/m3/src/x/net" "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" ) const ( @@ -113,6 +114,12 @@ type ConnectionOptions interface { // RWOptions returns the RW options. RWOptions() xio.Options + // SetTLSOptions sets TLS options + SetTLSOptions(value xtls.Options) ConnectionOptions + + // TLSOptions returns the TLS options + TLSOptions() xtls.Options + // ContextDialer allows customizing the way an aggregator client the aggregator, at the TCP layer. // By default, this is: // (&net.ContextDialer{}).DialContext. This can be used to do a variety of things, such as forwarding a connection @@ -137,6 +144,7 @@ type connectionOptions struct { maxThreshold int multiplier int connKeepAlive bool + tlsOptions xtls.Options dialer xnet.ContextDialerFn } @@ -159,6 +167,7 @@ func NewConnectionOptions() ConnectionOptions { multiplier: defaultReconnectThresholdMultiplier, maxDuration: defaultMaxReconnectDuration, writeRetryOpts: defaultWriteRetryOpts, + tlsOptions: xtls.NewOptions(), rwOpts: xio.NewOptions(), dialer: nil, // Will default to net.Dialer{}.DialContext } @@ -274,6 +283,16 @@ func (o *connectionOptions) RWOptions() xio.Options { return o.rwOpts } +func (o *connectionOptions) SetTLSOptions(value xtls.Options) ConnectionOptions { + opts := *o + opts.tlsOptions = value + return &opts +} + +func (o *connectionOptions) TLSOptions() xtls.Options { + return o.tlsOptions +} + func (o *connectionOptions) ContextDialer() xnet.ContextDialerFn { return o.dialer } diff --git a/src/aggregator/client/conn_test.go b/src/aggregator/client/conn_test.go index c868ca6098..36e2882bee 100644 --- a/src/aggregator/client/conn_test.go +++ b/src/aggregator/client/conn_test.go @@ -22,10 +22,13 @@ package client import ( "context" + "crypto/tls" + "crypto/x509" "errors" "fmt" "math" "net" + "os" "sync" "testing" "time" @@ -38,6 +41,7 @@ import ( "github.com/stretchr/testify/require" "github.com/m3db/m3/src/x/clock" + xtls "github.com/m3db/m3/src/x/tls" ) const ( @@ -405,6 +409,78 @@ func TestConnectWriteToServer(t *testing.T) { require.Nil(t, conn.conn) } +func TestTLSConnectWriteToServer(t *testing.T) { + data := []byte("foobar") + + // Start tls server. + var wg sync.WaitGroup + wg.Add(1) + + serverCert, err := tls.LoadX509KeyPair("./testdata/server.crt", "./testdata/server.key") + require.NoError(t, err) + certPool := x509.NewCertPool() + certs, err := os.ReadFile("./testdata/rootCA.crt") + require.NoError(t, err) + certPool.AppendCertsFromPEM(certs) + l, err := tls.Listen(tcpProtocol, testLocalServerAddr, &tls.Config{ + Certificates: []tls.Certificate{serverCert}, + ClientCAs: certPool, + ClientAuth: tls.RequireAndVerifyClientCert, + MinVersion: tls.VersionTLS13, + }) + t.Cleanup(func() { l.Close() }) // nolint: errcheck + require.NoError(t, err) + serverAddr := l.Addr().String() + + go func() { + defer wg.Done() + + // Ignore the first testing connection. + conn, err := l.Accept() + require.NoError(t, err) + tlsConn, ok := conn.(*tls.Conn) + require.True(t, ok) + err = tlsConn.Handshake() + require.NoError(t, err) + require.NoError(t, conn.Close()) + + // Read from the second connection. + conn, err = l.Accept() + require.NoError(t, err) + buf := make([]byte, 1024) + n, err := conn.Read(buf) + require.NoError(t, err) + require.Equal(t, data, buf[:n]) + conn.Close() // nolint: errcheck + }() + + clientCert, err := tls.LoadX509KeyPair("./testdata/client.crt", "./testdata/client.key") + require.NoError(t, err) + // Wait until the server starts up. + dialer := net.Dialer{Timeout: time.Minute} + // #nosec G402 + testConn, err := tls.DialWithDialer(&dialer, tcpProtocol, serverAddr, &tls.Config{ + InsecureSkipVerify: true, + Certificates: []tls.Certificate{clientCert}, + RootCAs: certPool, + }) + require.NoError(t, err) + require.NoError(t, testConn.Close()) + + // Create a new connection and assert we can write successfully. + opts := testTLSConnectionOptions().SetInitReconnectThreshold(0) + conn := newConnection(serverAddr, opts) + require.NoError(t, conn.Write(data)) + require.Equal(t, 0, conn.numFailures) + require.NotNil(t, conn.conn) + + wg.Wait() + + // Close the connection + conn.Close() + require.Nil(t, conn.conn) +} + func testConnectionOptions() ConnectionOptions { return NewConnectionOptions(). SetClockOptions(clock.NewOptions()). @@ -416,6 +492,17 @@ func testConnectionOptions() ConnectionOptions { SetWriteTimeout(100 * time.Millisecond) } +func testTLSConnectionOptions() ConnectionOptions { + tlsOptions := xtls.NewOptions(). + SetClientEnabled(true). + SetInsecureSkipVerify(true). + SetCAFile("./testdata/rootCA.crt"). + SetCertFile("./testdata/client.crt"). + SetKeyFile("./testdata/client.key"). + SetCertificatesTTL(time.Second) + return testConnectionOptions().SetTLSOptions(tlsOptions) +} + func testConnectionProperties() *gopter.Properties { params := gopter.DefaultTestParameters() params.Rng.Seed(testRandomSeeed) diff --git a/src/aggregator/client/testdata/client.crt b/src/aggregator/client/testdata/client.crt new file mode 100644 index 0000000000..366d36e8b8 --- /dev/null +++ b/src/aggregator/client/testdata/client.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKwMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDE1MTQ0NjEyWhcNMzQwNDEzMTQ0NjEyWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0Q2xpZW50Q2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMXgv5tgZD5iBW3LfTC27jPHTLlsAw8ZlBFLmd7potdS3Hy512Xa +kKOaKRKH/0Yla4BKGNh0ZP+xege15CpTFqwtzDPCRkaRvfSxRW19MukeergRDqqI +tEwURHQ5gfub7XB9e5wuNmW2GGs/xgiWsxxm6UMidXkhA5a2//OmDSm2JdcoxJbk +1m1OUzXUuLKLzCIRAbJFFIM1RpvjWSonxpJ2lsNCVQ+LdItxE9mAI+Y+PaDonyzg +ehfAqkl+wxGRevaDFFJr1defxmnJhaYv40AcEpXOWeetZFlspEtrPF7b/3GifRdx +Mlwq27wn3mk4wF1wa12kRNKiz+HHjZrB7UECAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAKblZ+C4Pdv5grKiCQcfFt53SbIys8uavxmzJNn9nq1QVgBmR/hX8yG8ULeh5 +kKrccwe2WXCBWsIj9cvGCzR+MPMvuIXd3d58yLybmIUBDfJSBC0v5TaNWP2ZdJDG +fH38DfQTeE9XcjpRtCGYtYwReBptSlNiYQlvwkukTNz3mV+0BpvQu/9/R0BNCJAF +9VeLgUcG7T7HC+foCeU7h83Y0xH0OALe33ntcrMPHUclXqImCiC7dl8pb3H7o/xJ +jS3KZxfv6ioxpirvpEbX/EeF+H1HQZUZ8y3+J3K37jMgzNamd/xsaJxEfCq4SE+n +HmgeXgqoNjrM6J0jYAfx1MjbIA== +-----END CERTIFICATE----- diff --git a/src/aggregator/client/testdata/client.key b/src/aggregator/client/testdata/client.key new file mode 100644 index 0000000000..ff226a549e --- /dev/null +++ b/src/aggregator/client/testdata/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxeC/m2BkPmIFbct9MLbuM8dMuWwDDxmUEUuZ3umi11LcfLnX +ZdqQo5opEof/RiVrgEoY2HRk/7F6B7XkKlMWrC3MM8JGRpG99LFFbX0y6R56uBEO +qoi0TBREdDmB+5vtcH17nC42ZbYYaz/GCJazHGbpQyJ1eSEDlrb/86YNKbYl1yjE +luTWbU5TNdS4sovMIhEBskUUgzVGm+NZKifGknaWw0JVD4t0i3ET2YAj5j49oOif +LOB6F8CqSX7DEZF69oMUUmvV15/GacmFpi/jQBwSlc5Z561kWWykS2s8Xtv/caJ9 +F3EyXCrbvCfeaTjAXXBrXaRE0qLP4ceNmsHtQQIDAQABAoIBAAtyP8MuJT5SjzvV +rI0317mZCsAjFl42PZFujR0O6MOJ4IU6ftI+fWVpUnzm7wZQvdIy9xL2UK1Vx9hQ +Vj14hvQ4xfosf8IvRgy0gG6f8mT3xWOGYRHOTJemCHuso+85CtgZ+h+DsNPbX7g8 +fSkcBopbDZ07jg4OsdVzCoU+kr5Z1/VDe0O0rTDzGVP686tN9I+DmDYv2HlBUaSZ +SyNZrLJFziqFx4ATTIE3aTLd29pzTl09WiCcZSxhlS+ROnN2iW2xDLNgY9SdUo2a +3r8N+xhYDNQz2paxlsv2x/tCRJvrQDoX7S5QZw4P1pVr6wo6xC+BU2UlpVHifo4M +egyDbAECgYEA5Y9+JDOSQF2cvIV4xTMgVnkLig/ngYesS44xz8HYTRR5s1WW6oCX +3j+OosZBbJpXeBlTK31G9Z92+Om2/Cj2uDvff7EJdhZ5sAdMtUy5X7p9rGTEyi5F +ecZDBfmwHH0lFdwpNtlky1uoaUsVd4IS28WMHoHYDGW241IW8xOlmeECgYEA3Ksb +KR7JCbHx21wh5oPVn5BLpdag7D+M1jBFtjPrzLDNhXlsV9zkjn7xETvTXWU2vNlh +OkoRu4VFEAnsgtxh+gT0lo9ZG9IWi2qP99vPpB3AFYuujBu3GA02/iOo5pri/6R0 +ZNEEreAaaOcT2z6K9HG0KMQ6QpYgXSlzJW8sf2ECgYEAvn2fMA03bIAB4xJi0EkH +qZoScEOYWQ0rdRsOzJbPlc7K2nzImdmRrFRTWVFo0uUUdk2VjX4Mlx/3ir/uHzsi +2GieowhWkI4/9kloZv2+yegoBxkrj5ZsAov57AhxEoLqdkRWUvR8xp9Nleo/awcd +/Q7loh8fF9KDvAjPkHAaOCECgYA98bZNI7wxgYcwGbvWdrmX8iyaIBbKWsiRM7nN +/OM7cYIv7rbwLyzlp1LKkK2zsP7donP9pd82caHCb9a5oV3LjmqOfSz5d08m0cIa +RNUT79oE8lIMOJd8I/GFA8OdAGuqcaLOzjHvEVK4ke1sBTGCjwyQyQzFtljdbg5J +utyV4QKBgHkSBKwrgaump08MJFfK9uFo5sr5jy692XYWMlBVhc+piPwUsoztjI1O +8cvie0mMj6oofSoSyD4FUg9kFUCRMPC0kga/zlHyL1Y/uILX4XUFMl4P/gMbC7kw +0d6BdEsp5SU8Onc9eV/EW+q2Mz7/t5ZHkUrdDQYGgkIBqRZcXlka +-----END RSA PRIVATE KEY----- diff --git a/src/aggregator/client/testdata/rootCA.crt b/src/aggregator/client/testdata/rootCA.crt new file mode 100644 index 0000000000..5d92901e85 --- /dev/null +++ b/src/aggregator/client/testdata/rootCA.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIUUwZEZu6XUKW03HPLHGDlt/d/BeUwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKVGVzdFJvb3RDQTAe +Fw0yNDA0MDQxMTAwMzhaFw0zNDAxMDIxMTAwMzhaMFoxCzAJBgNVBAYTAk5MMRMw +EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDRwn7QLv6zup98kB9VJ1HvHQQMZxt2tqhpFGEc0bXiGrmhFKjV +XZ264ALnUJHQgM8rkRe+dTXg8455FPTMoT+O/BC44pt8CaY1C5kQzVkLaMwaVVxV +vsBfdZ9LOKJQmUXIf1qV/UK3P55Oc3TvwSQW3tU0eMMEOnuNdA4lDdlQnExb7tIJ +4sl0i2w6rB7CoS/0gx2eUPLhPK+vcwCM3d81Odb3ULVznyJ5ZUd5mYY85XXVFQdW +QSyaEE0ekzbmXQzjiqNAt67WEmbadoKdeg8jPG5ka8qBZ21ZGjCHQ8AbzpOn+eDk +n50zwxncpSYVxTilV70PbFSY33PdmdS6XjEnAgMBAAGjUzBRMB0GA1UdDgQWBBSo +kzZddlleTXZYQsBc8efmsZj3WTAfBgNVHSMEGDAWgBSokzZddlleTXZYQsBc8efm +sZj3WTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQACb1ZT3WPK +XdO6jYbZuMN5BBET+mNKQOYrdehY5oiLgr5sDwTwfZvf+q6ic7MpDRBOWMSQuYwp +aUxK7qfw4nSSZb4zDPpzB6Tauh7M5TTPYQwzesn9/JaUBO4vCTwOFGuWxEbXe6PF +VuQOyGQCR9a7juNgoDLiRpWi+xXHz1kMNSeZqV8bn7eo/SIziuSW1JScse91Brt1 +Db9L8iSJpKLDLtwv8fU4/NSORc/672uy4OLxNppsTthh1rk94KbC/FYllC6e8N8n +0iffEn4xE/CECYmAhvplXXS20hCTGbs7tPJ3DGsRfHOyI9RXiayNZuq9GIYKv6eR ++h7NYwXua7oP +-----END CERTIFICATE----- diff --git a/src/aggregator/client/testdata/rootCA.key b/src/aggregator/client/testdata/rootCA.key new file mode 100644 index 0000000000..6d8a527ee2 --- /dev/null +++ b/src/aggregator/client/testdata/rootCA.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDRwn7QLv6zup98 +kB9VJ1HvHQQMZxt2tqhpFGEc0bXiGrmhFKjVXZ264ALnUJHQgM8rkRe+dTXg8455 +FPTMoT+O/BC44pt8CaY1C5kQzVkLaMwaVVxVvsBfdZ9LOKJQmUXIf1qV/UK3P55O +c3TvwSQW3tU0eMMEOnuNdA4lDdlQnExb7tIJ4sl0i2w6rB7CoS/0gx2eUPLhPK+v +cwCM3d81Odb3ULVznyJ5ZUd5mYY85XXVFQdWQSyaEE0ekzbmXQzjiqNAt67WEmba +doKdeg8jPG5ka8qBZ21ZGjCHQ8AbzpOn+eDkn50zwxncpSYVxTilV70PbFSY33Pd +mdS6XjEnAgMBAAECggEAc7MZadGSMibNU9LXRYwDuKXDlufEkU2dRSC1lETkU0w0 +efJAUjhqCSsIzvh1BZIXM8u7UhWq22LcglMZ2ElVGpRU0PZFlzxnTjkQe+EYrZ9n +fVr6LF2kFMN3UvFxLo3snPQKTlM82oEf29v5c0mQk4DkDyZRQ2LSxapvJuclqD0e +iMEG4Yad7p86AmJm7vpe9yp0POhfF5k9ZOAmrov67LMrFPRpWjeRr2ISa2Y/pjYI +vMqL+jGQi72rasNC0JYKyqn+v+LzocZGL4rCQUIpNmKkw81VolwkU8mfe87kneqh +lBsUgmg9nGtNnoFoxxeBNCEQGXf9+BhdGujujycmUQKBgQDo+a+/l/hn3E+7Tqup +nTAuZn80ODpQkfQ/L+jm6G0ENkil62kdyQCUZFfZqpj9dQwSPzCCViXrZymTL35G +1pzZqejPYGANRmiMeQMOOvyUwfIaEz3U9aj7hWl1v4WLUPzEUTTOsG8DBsR/lHBg +ZC081DH8U3m9fYN+Rp6xdT8z+QKBgQDmfXNfrNjFyxnB+kTJovM0k0f6p5HLKjF3 +VNFz0vpNgsa7DHXrmHSBBENi9oj4z72UQo1Q3bTRezR2TLZpsUmkGNEL0PoXTE1U +YVEb/VStLIZeKFv83uD2qvaYw2gA5785SNeinpweNhTC55T0ixojUmVA9eeIT/ex +nBDDJBuWHwKBgDQ12JQIW6vy7I9edwwA5Q5Q/ArY2wC5ZNJQS1KMHfpGrAs68Yiy +RgX7YtCt8bFDbNwd+yIKal8R9Hg+uX7ok6gX8suenof7Em0ApZWn1HkF6dq8GyxB +jYgogtTXgfcRxEO+qyXy1j4IYzrwKir/6D9sknMoxeyYV0KSUvgT/YEJAoGAaCIb +gwlLcqlc/Md+Vn75VDKKXZNhiiGI8bnvW13hWi2Qbaemiwd482UisM5jec4Zf6dF +w1g3PkFkpWHpM/02IR5ZK/aBVw9RDKNfCr88h3TLTDT9wlRL3QXGnaQDFA2f1liz +m7P/IqMaZChOouFJsNWkC2JN9cbzSFoTNKbWk88CgYAS37I31C6xyWlucbztFpmv +YqEPYRpIbRnJUq+F2Kof8dfT44C3rYwRxPHp2LMgyuPxdE491+Gfwsiv587KuBOP +XpCLuWNN38UDndQ5Zcaxl025akSnAF28EitjZCWzw+8TO/52358vpypwSYkdvQNx +or4Q8/AazdVqWUDo3sARPQ== +-----END PRIVATE KEY----- diff --git a/src/aggregator/client/testdata/server.crt b/src/aggregator/client/testdata/server.crt new file mode 100644 index 0000000000..3618a40052 --- /dev/null +++ b/src/aggregator/client/testdata/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKvMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDA0MTE0ODIwWhcNMzEwNzA3MTE0ODIwWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0U2VydmVyQ2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOoGyqgrsBCufd9UNt6wr9UJzPaVqdrr/Zble2UPMVZOynAT69W5 +gMdNvrIDU6l1EnRkLyxmP7+beYZvWyU+kN9LyP0pdcgSQdtyhbr1CkqMxhM1MA2B +OFvcEFN2rybaqcLDlq2WUQkmyuVQt7/weLU9jC596PtxUL1e0+wqIItISWpcmemf +cwH9TVlD8Yh6emf87v27afxc0bZCauPsrEPvOC00oU4epXnA7TeDs4RtfqVAuVHX +oEG3oNZ3NHXIIgSht3rNAEfOxBX+7KGvW4hW9ci6W758ECGzHFJThhzO3+w+0YPP +29m+v4CVRaXAGxkVRPiAUDpU8RSPteywTyMCAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAuQME+WSPw1KKK1jgh1wuiyM5hou1BeagfwOipM3CEblTiHfoERsd2f7Oi6e2 +zPACrvKjVP6bHKjZPEztFHGXDuLdBqK97Vd/o4IFl6p/8EuBeWV85o4Rk1PMhqBI +0jSxsXXhBLgSTkUBgSllwgAbAaxm+L3wBJiQOhh/wEdfNSJ/GeL5477J56Q12/hA +rRCghYRAVVpr44NfG/PbuuMklQHK2l+wkJFBnx+zAAtZF/nb5yCVSy8z/p2TT72a +STfegXYJtE3p4X2+78xajrI7x/PWhNlzJRn3txLW6461vYJIB3ZDVUA2LZtmi3nd +9BUuJVSvsoVwlvrUBF0S1P0vWg== +-----END CERTIFICATE----- diff --git a/src/aggregator/client/testdata/server.key b/src/aggregator/client/testdata/server.key new file mode 100644 index 0000000000..6b6e1cd4cc --- /dev/null +++ b/src/aggregator/client/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6gbKqCuwEK5931Q23rCv1QnM9pWp2uv9luV7ZQ8xVk7KcBPr +1bmAx02+sgNTqXUSdGQvLGY/v5t5hm9bJT6Q30vI/Sl1yBJB23KFuvUKSozGEzUw +DYE4W9wQU3avJtqpwsOWrZZRCSbK5VC3v/B4tT2MLn3o+3FQvV7T7Cogi0hJalyZ +6Z9zAf1NWUPxiHp6Z/zu/btp/FzRtkJq4+ysQ+84LTShTh6lecDtN4OzhG1+pUC5 +UdegQbeg1nc0dcgiBKG3es0AR87EFf7soa9biFb1yLpbvnwQIbMcUlOGHM7f7D7R +g8/b2b6/gJVFpcAbGRVE+IBQOlTxFI+17LBPIwIDAQABAoIBAG7rZSYr/rkJWxN8 +wLzvlYctq/27ldPzQjBA+Ck/+CxmD8DrUD3uPEE6cgXBWefZWzPbQBiVJeXU9NZo +9RREATo0m0CSZAa90IUIVKX1+ji3XDJCZVIhSusO9JX6jK2nugpb1ZDUy/fAcS4b +AdxxR2TvlP98Ie77GD+JMHygwugAJPQ4AFZQjO2F/2k3crVt9Frt1VSvf72nkM/C +sDJy4kv+lDZsbeveNPzJo1honiKdCDWpg4duLiLkkG4YvftWHGzpXHoOWnuwnrT5 +jrQTaG10d1jMpnxOwDrxRsFYWtz4kbJm2VtRsgWRIy4A4Vv8VKfFJ0cSu0CChjTE +7yVrExECgYEA9orYMAuuu/c0HGVcXOsA9x6XHtBXr5wK1fI973sGUrtkshTlBgjG +Bc7H+AhVBVdqr2ipm9pFZYSV1OYB2+3AgMQjhoZANR9jr5bmRjJgWuBi5umrFKa4 +n0IQbkwSzjxKvkhpJK25BY2kpjh7/128Q80PMkKnm0LR2gyUWvLFuUUCgYEA8wEJ +PyxHBu93NnbwTP0SUEOYFa654ctiVLbicdcUsbo/Qo4gl/0AIpGmMdeKqx3FWn9I +kCH1xlvb7hp7FNFhDZIiw6CT8V2RQHXdEO9/S0jjOOBg29hxARIgoU+PjjaQG/H+ +SqlDR/ijXUghlllwtP6jCJqy29+Ukpx8WnIkiUcCgYEA4DNXcinNlyWw//m1StJz +KEdrShxRAWO3/uOyKywHjgjYA5I4y4oC+pkXBJofJyJd5HIbe5286hruXpNkMWo+ +W1sCq30+upB3COEDO+bn9PyFNFRevgL3deyQIJ2RpHbzyaLz6uUIkuJCTt6i9JF7 +eXTrUtuGrAzR5ab26tZHtLkCgYBZtW42diAafVjjMHFx2pNiMjH4/zH8G1tHruRV +5nLrUbh8139M3Mep+18BU3MLzMfIquic3Qygl9TPR9NcUhekLOjMk4FKP6KZy2k7 +EU9K+bN4kczwHC1LmeeV5Ys3kwkMZ//ajDYcYbvdef18RbSH/OEvEf0pkpaEsWRu +92hpjQKBgH6Y3dW0bQIhXikjboD00vPn0zPuh35I9OfrPHMqDJiz+xom7Sz9tlGk +NfFll5PTf0gZtU7160uzhCp/OqXT3IdVgVt/G5ey13oYP7pCb8MWiuyNW0yUgtTp +GM5GUnEW5zJ1t2qJOxwoq/FA7D5C7EGRM/QpoMD0tsJoJKYyWkJO +-----END RSA PRIVATE KEY----- diff --git a/src/cmd/services/m3aggregator/config/server.go b/src/cmd/services/m3aggregator/config/server.go index e063c6c549..6f595a8c2d 100644 --- a/src/cmd/services/m3aggregator/config/server.go +++ b/src/cmd/services/m3aggregator/config/server.go @@ -79,6 +79,9 @@ type RawTCPServerConfiguration struct { // Protobuf iterator configuration. ProtobufIterator protobufUnaggregatedIteratorConfiguration `yaml:"protobufIterator"` + + // TLS configuration + TLS *xserver.TLSConfiguration `yaml:"tls"` } // NewServerOptions create a new set of raw TCP server options. @@ -97,6 +100,9 @@ func (c *RawTCPServerConfiguration) NewServerOptions( if c.KeepAlivePeriod != nil { serverOpts = serverOpts.SetTCPConnectionKeepAlivePeriod(*c.KeepAlivePeriod) } + if c.TLS != nil { + serverOpts = serverOpts.SetTLSOptions(c.TLS.NewOptions()) + } opts = opts.SetServerOptions(serverOpts) // Set protobuf iterator options. diff --git a/src/x/server/config.go b/src/x/server/config.go index d49c1575da..a5333cd562 100644 --- a/src/x/server/config.go +++ b/src/x/server/config.go @@ -25,6 +25,7 @@ import ( "github.com/m3db/m3/src/x/instrument" "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" ) // Configuration configs a server. @@ -40,6 +41,9 @@ type Configuration struct { // KeepAlive period. KeepAlivePeriod *time.Duration `yaml:"keepAlivePeriod"` + + // TLS configuration + TLS *TLSConfiguration `yaml:"tls"` } // NewOptions creates server options. @@ -53,6 +57,9 @@ func (c Configuration) NewOptions(iOpts instrument.Options) Options { if c.KeepAlivePeriod != nil { opts = opts.SetTCPConnectionKeepAlivePeriod(*c.KeepAlivePeriod) } + if c.TLS != nil { + opts = opts.SetTLSOptions(c.TLS.NewOptions()) + } return opts } @@ -60,3 +67,42 @@ func (c Configuration) NewOptions(iOpts instrument.Options) Options { func (c Configuration) NewServer(handler Handler, iOpts instrument.Options) Server { return NewServer(c.ListenAddress, handler, c.NewOptions(iOpts)) } + +// TLSConfiguration configs a tls server +type TLSConfiguration struct { + // Mode is the tls server mode + // disabled - allows plaintext connections only + // permissive - allows both plaintext and TLS connections + // enforced - allows TLS connections only + Mode string `yaml:"mode" validate:"nonzero,regexp=^(disabled|permissive|enforced)$"` + + // MutualTLSEnabled sets mTLS + MutualTLSEnabled bool `yaml:"mTLSEnabled"` + + // CertFile path to a server certificate file + CertFile string `yaml:"certFile"` + + // KeyFile path to a server key file + KeyFile string `yaml:"keyFile"` + + // ClientCAFile path to a CA file for verifying clients + ClientCAFile string `yaml:"clientCAFile"` + + // CertificatesTTL is a time duration certificates are stored in memory + CertificatesTTL time.Duration `yaml:"certificatesTTL"` +} + +// NewOptions creates TLS options +func (c TLSConfiguration) NewOptions() xtls.Options { + opts := xtls.NewOptions(). + SetMutualTLSEnabled(c.MutualTLSEnabled). + SetCertFile(c.CertFile). + SetKeyFile(c.KeyFile). + SetCAFile(c.ClientCAFile). + SetCertificatesTTL(c.CertificatesTTL) + var tlsMode xtls.ServerMode + if err := tlsMode.UnmarshalText([]byte(c.Mode)); err == nil { + opts = opts.SetServerMode(tlsMode) + } + return opts +} diff --git a/src/x/server/config_test.go b/src/x/server/config_test.go index d2284efd44..a0ffb8d4c7 100644 --- a/src/x/server/config_test.go +++ b/src/x/server/config_test.go @@ -28,6 +28,7 @@ import ( yaml "gopkg.in/yaml.v2" "github.com/m3db/m3/src/x/instrument" + xtls "github.com/m3db/m3/src/x/tls" ) func TestServerConfiguration(t *testing.T) { @@ -35,6 +36,13 @@ func TestServerConfiguration(t *testing.T) { listenAddress: addr keepAliveEnabled: true keepAlivePeriod: 5s +tls: + mode: enforced + mTLSEnabled: true + certFile: /tmp/cert + keyFile: /tmp/key + clientCAFile: /tmp/ca + certificatesTTL: 10m ` var cfg Configuration @@ -43,9 +51,23 @@ keepAlivePeriod: 5s require.True(t, *cfg.KeepAliveEnabled) require.Equal(t, 5*time.Second, *cfg.KeepAlivePeriod) + require.Equal(t, "enforced", cfg.TLS.Mode) + require.True(t, cfg.TLS.MutualTLSEnabled) + require.Equal(t, "/tmp/cert", cfg.TLS.CertFile) + require.Equal(t, "/tmp/key", cfg.TLS.KeyFile) + require.Equal(t, "/tmp/ca", cfg.TLS.ClientCAFile) + require.Equal(t, 10*time.Minute, cfg.TLS.CertificatesTTL) + opts := cfg.NewOptions(instrument.NewOptions()) require.Equal(t, 5*time.Second, opts.TCPConnectionKeepAlivePeriod()) require.True(t, opts.TCPConnectionKeepAlive()) + require.Equal(t, xtls.Enforced, opts.TLSOptions().ServerMode()) + require.True(t, opts.TLSOptions().MutualTLSEnabled()) + require.Equal(t, "/tmp/cert", opts.TLSOptions().CertFile()) + require.Equal(t, "/tmp/key", opts.TLSOptions().KeyFile()) + require.Equal(t, "/tmp/ca", opts.TLSOptions().CAFile()) + require.Equal(t, 10*time.Minute, opts.TLSOptions().CertificatesTTL()) + require.NotNil(t, cfg.NewServer(nil, instrument.NewOptions())) } diff --git a/src/x/server/options.go b/src/x/server/options.go index 46e61370b9..3cab5ec435 100644 --- a/src/x/server/options.go +++ b/src/x/server/options.go @@ -26,6 +26,7 @@ import ( "github.com/m3db/m3/src/x/instrument" xnet "github.com/m3db/m3/src/x/net" "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" ) const ( @@ -71,6 +72,12 @@ type Options interface { // ListenerOptions sets the listener options for the server. ListenerOptions() xnet.ListenerOptions + + // SetTLSOptions sets the tls options for the server + SetTLSOptions(value xtls.Options) Options + + // TLSOptions returns the tls options for the server + TLSOptions() xtls.Options } type options struct { @@ -79,6 +86,7 @@ type options struct { tcpConnectionKeepAlive bool tcpConnectionKeepAlivePeriod time.Duration listenerOpts xnet.ListenerOptions + tlsOptions xtls.Options } // NewOptions creates a new set of server options @@ -89,6 +97,7 @@ func NewOptions() Options { tcpConnectionKeepAlive: defaultTCPConnectionKeepAlive, tcpConnectionKeepAlivePeriod: defaultTCPConnectionKeepAlivePeriod, listenerOpts: xnet.NewListenerOptions(), + tlsOptions: xtls.NewOptions(), } } @@ -141,3 +150,13 @@ func (o *options) SetListenerOptions(value xnet.ListenerOptions) Options { func (o *options) ListenerOptions() xnet.ListenerOptions { return o.listenerOpts } + +func (o *options) SetTLSOptions(value xtls.Options) Options { + opts := *o + opts.tlsOptions = value + return &opts +} + +func (o *options) TLSOptions() xtls.Options { + return o.tlsOptions +} diff --git a/src/x/server/secured_conn.go b/src/x/server/secured_conn.go new file mode 100644 index 0000000000..a975a59a78 --- /dev/null +++ b/src/x/server/secured_conn.go @@ -0,0 +1,104 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package server implements a network server. +package server + +import ( + "crypto/tls" + "net" +) + +// TLSHandshakeFirstByte is the first byte of a tls connection handshake +const TLSHandshakeFirstByte = 0x16 + +func newSecuredConn(conn net.Conn) *securedConn { + return &securedConn{ + Conn: conn, + } +} + +type securedConn struct { + net.Conn + isTLS *bool + peekedByte byte + peekedByteIsSet bool +} + +// IsTLS returns is the connection is TLS or not. +// It peeks at the first byte and checks +// if it is equal to the TLS handshake first byte +// https://www.rfc-editor.org/rfc/rfc5246#appendix-A.1 +func (s *securedConn) IsTLS() (bool, error) { + if s.isTLS != nil { + return *s.isTLS, nil + } + firstByte, err := s.peek() + if err != nil { + return false, err + } + isTLS := firstByte == TLSHandshakeFirstByte + s.isTLS = &isTLS + + return isTLS, nil +} + +func (s *securedConn) UpgradeToTLS(tlsConfig *tls.Config) *securedConn { + tlsConn := tls.Server(s, tlsConfig) + t := true + return &securedConn{ + Conn: tlsConn, + isTLS: &t, + } +} + +// Read reads n bytes +func (s *securedConn) Read(n []byte) (int, error) { + // Before reading we need to ensure if we know the type of the connection. + // After reading data it will be impossible to determine + // if the connection has the TLS layer or not. + if s.isTLS == nil { + if _, err := s.IsTLS(); err != nil { + return 0, err + } + } + if s.peekedByteIsSet && len(n) > 0 { + n[0] = s.peekedByte + s.peekedByteIsSet = false + n, err := s.Conn.Read(n[1:]) + return n + 1, err + } + return s.Conn.Read(n) +} + +func (s *securedConn) peek() (byte, error) { + if s.peekedByteIsSet { + return s.peekedByte, nil + } + + var buf [1]byte + _, err := s.Conn.Read(buf[:]) + if err != nil { + return 0, err + } + s.peekedByte = buf[0] + s.peekedByteIsSet = true + return buf[0], nil +} diff --git a/src/x/server/secured_conn_test.go b/src/x/server/secured_conn_test.go new file mode 100644 index 0000000000..c4e0fd5dd4 --- /dev/null +++ b/src/x/server/secured_conn_test.go @@ -0,0 +1,132 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +// Package server implements a network server. +package server + +import ( + "crypto/tls" + "net" + "testing" + + "github.com/stretchr/testify/require" +) + +func testTCPServer(connCh chan *securedConn, errCh chan error) (net.Listener, error) { + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + go func(net.Listener, chan *securedConn, chan error) { + conn, err := listener.Accept() + if err != nil { + errCh <- err + } else { + securedConn := newSecuredConn(conn) + isTLS, err := securedConn.IsTLS() + if err != nil { + errCh <- err + return + } + if isTLS { + certs, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") + if err != nil { + errCh <- err + return + } + tlsConfig := tls.Config{Certificates: []tls.Certificate{certs}, MinVersion: tls.VersionTLS13} + securedConn = securedConn.UpgradeToTLS(&tlsConfig) + tlsConn := securedConn.Conn.(*tls.Conn) + if err = tlsConn.Handshake(); err != nil { + errCh <- err + } + } + connCh <- securedConn + } + }(listener, connCh, errCh) + return listener, nil +} + +func TestPlainTCPConnection(t *testing.T) { + connCh := make(chan *securedConn) + errCh := make(chan error) + listener, err := testTCPServer(connCh, errCh) + require.NoError(t, err) + defer listener.Close() // nolint: errcheck + + clientConn, err := net.Dial("tcp", listener.Addr().String()) + require.NoError(t, err) + data := []byte("not a tls connection") + _, err = clientConn.Write(data) + require.NoError(t, err) + + var conn *securedConn + select { + case newConn := <-connCh: + conn = newConn + case newErr := <-errCh: + err = newErr + } + require.NoError(t, err) + defer conn.Close() // nolint: errcheck + + isTLS, err := conn.IsTLS() + require.NoError(t, err) + require.False(t, isTLS) + result := make([]byte, len(data)) + _, err = conn.Read(result) + require.NoError(t, err) + require.Equal(t, data, result) +} + +func TestTLSConnection(t *testing.T) { + connCh := make(chan *securedConn) + errCh := make(chan error) + listener, err := testTCPServer(connCh, errCh) + require.NoError(t, err) + defer listener.Close() // nolint: errcheck + + clientConn, err := tls.Dial("tcp", listener.Addr().String(), &tls.Config{InsecureSkipVerify: true}) // #nosec G402 + require.NoError(t, err) + defer clientConn.Close() // nolint: errcheck + + data := []byte("tls connection") + _, err = clientConn.Write(data) + require.NoError(t, err) + + var serverConn *securedConn + select { + case newConn := <-connCh: + serverConn = newConn + case newErr := <-errCh: + err = newErr + } + require.NoError(t, err) + defer serverConn.Close() // nolint: errcheck + + isTLS, err := serverConn.IsTLS() + require.NoError(t, err) + require.True(t, isTLS) + + result := make([]byte, len(data)) + _, err = serverConn.Read(result) + require.NoError(t, err) + require.Equal(t, data, result) +} diff --git a/src/x/server/server.go b/src/x/server/server.go index fbe502da3e..a2c8937e4a 100644 --- a/src/x/server/server.go +++ b/src/x/server/server.go @@ -22,6 +22,7 @@ package server import ( + "fmt" "net" "sync" "sync/atomic" @@ -32,6 +33,7 @@ import ( xnet "github.com/m3db/m3/src/x/net" "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" ) // Server is a server capable of listening to incoming traffic and closing itself @@ -60,12 +62,14 @@ type Handler interface { } type serverMetrics struct { - openConnections tally.Gauge + openConnections tally.Gauge + upgradeToTLSErrors tally.Counter } func newServerMetrics(scope tally.Scope) serverMetrics { return serverMetrics{ - openConnections: scope.Gauge("open-connections"), + openConnections: scope.Gauge("open-connections"), + upgradeToTLSErrors: scope.Counter("upgrade-to-tls-errors"), } } @@ -83,14 +87,15 @@ type server struct { tcpConnectionKeepAlive bool tcpConnectionKeepAlivePeriod time.Duration - closed bool - closedChan chan struct{} - numConns int32 - conns []net.Conn - wgConns sync.WaitGroup - metrics serverMetrics - handler Handler - listenerOpts xnet.ListenerOptions + closed bool + closedChan chan struct{} + numConns int32 + conns []net.Conn + wgConns sync.WaitGroup + metrics serverMetrics + handler Handler + listenerOpts xnet.ListenerOptions + tlsConfigManager xtls.ConfigManager addConnectionFn addConnectionFn removeConnectionFn removeConnectionFn @@ -112,6 +117,7 @@ func NewServer(address string, handler Handler, opts Options) Server { metrics: newServerMetrics(scope), handler: handler, listenerOpts: opts.ListenerOptions(), + tlsConfigManager: xtls.NewConfigManager(opts.TLSOptions(), instrumentOpts), } // Set up the connection functions. @@ -140,11 +146,32 @@ func (s *server) Serve(l net.Listener) error { return nil } +func (s *server) maybeUpgradeToTLS(conn *securedConn) (*securedConn, error) { + if s.tlsConfigManager.ServerMode() == xtls.Disabled { + return conn, nil + } + isTLSConnection, err := conn.IsTLS() + if err != nil { + return nil, err + } + if !isTLSConnection && s.tlsConfigManager.ServerMode() == xtls.Enforced { + return nil, fmt.Errorf("not a tls connection") + } else if !isTLSConnection { + return conn, nil + } + tlsConfig, err := s.tlsConfigManager.TLSConfig() + if err != nil { + return nil, err + } + conn = conn.UpgradeToTLS(tlsConfig) + return conn, nil +} + func (s *server) serve() { connCh, errCh := xnet.StartForeverAcceptLoop(s.listener, s.retryOpts) for conn := range connCh { - conn := conn - if tcpConn, ok := conn.(*net.TCPConn); ok { + conn := newSecuredConn(conn) + if tcpConn, ok := conn.Conn.(*net.TCPConn); ok { tcpConn.SetKeepAlive(s.tcpConnectionKeepAlive) if s.tcpConnectionKeepAlivePeriod != 0 { tcpConn.SetKeepAlivePeriod(s.tcpConnectionKeepAlivePeriod) @@ -155,11 +182,17 @@ func (s *server) serve() { } else { s.wgConns.Add(1) go func() { - s.handler.Handle(conn) - - conn.Close() - s.removeConnectionFn(conn) - s.wgConns.Done() + defer conn.Close() // nolint: errcheck + defer s.removeConnectionFn(conn) + defer s.wgConns.Done() + + securedConn, err := s.maybeUpgradeToTLS(conn) + if err != nil { + s.metrics.upgradeToTLSErrors.Inc(1) + s.log.Error("unable to upgrade connection to TLS", zap.Error(err)) + return + } + s.handler.Handle(securedConn) }() } } diff --git a/src/x/server/server_benchmark_test.go b/src/x/server/server_benchmark_test.go new file mode 100644 index 0000000000..afc83cff53 --- /dev/null +++ b/src/x/server/server_benchmark_test.go @@ -0,0 +1,155 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package server + +import ( + "bufio" + "crypto/tls" + "fmt" + "net" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" +) + +const ( + testBenchListenAddress = "127.0.0.1:0" +) + +func newMockKeepAliveHandler() *mockKeepAliveHandler { return &mockKeepAliveHandler{} } + +type mockKeepAliveHandler struct { + mockHandler +} + +func (h *mockKeepAliveHandler) Handle(conn net.Conn) { + defer conn.Close() // nolint: errcheck + + reader := bufio.NewReader(conn) + + for { + data, err := reader.ReadString('\n') + if err != nil { + break + } + _, err = conn.Write([]byte(data)) + if err != nil { + break + } + } +} + +// nolint: unparam +func testBenchServer(addr string, h Handler, tlsMode xtls.ServerMode, mTLSEnabled bool) *server { + tlsOpts := xtls.NewOptions(). + SetServerMode(tlsMode). + SetMutualTLSEnabled(mTLSEnabled). + SetCAFile("./testdata/rootCA.crt"). + SetCertFile("./testdata/server.crt"). + SetKeyFile("./testdata/server.key") + + opts := NewOptions().SetRetryOptions(retry.NewOptions().SetMaxRetries(2)) + opts = opts.SetInstrumentOptions(opts.InstrumentOptions().SetReportInterval(time.Second)).SetTLSOptions(tlsOpts) + + s := NewServer(addr, h, opts).(*server) + + s.addConnectionFn = func(conn net.Conn) bool { + ret := s.addConnection(conn) + return ret + } + + s.removeConnectionFn = func(conn net.Conn) { + s.removeConnection(conn) + } + + return s +} + +func dial(listenAddr string, tlsMode xtls.ServerMode, certs []tls.Certificate) (net.Conn, error) { + if tlsMode == xtls.Disabled { + return net.Dial("tcp", listenAddr) + } + return tls.Dial("tcp", listenAddr, &tls.Config{InsecureSkipVerify: true, Certificates: certs}) // #nosec G402 +} + +func benchmarkServer(tlsMode xtls.ServerMode, mTLSEnabled bool, b *testing.B) { + handler := newMockHandler() + server := testBenchServer(testBenchListenAddress, handler, tlsMode, mTLSEnabled) + b.Cleanup(func() { server.Close(); handler.Close() }) + err := server.ListenAndServe() + require.NoError(b, err) + cert, err := tls.LoadX509KeyPair("./testdata/client.crt", "./testdata/client.key") + require.NoError(b, err) + for n := 0; n < b.N; n++ { + conn, err := dial(server.listener.Addr().String(), tlsMode, []tls.Certificate{cert}) + require.NoError(b, err) + msg := fmt.Sprintf("msg%d", n) + _, err = conn.Write([]byte(msg)) + require.NoError(b, err) + } + waitFor(func() bool { return handler.called() == b.N }) + require.Equal(b, b.N, handler.called()) +} + +func BenchmarkPlainTCPServer(b *testing.B) { + benchmarkServer(xtls.Disabled, false, b) +} + +func BenchmarkTLSServer(b *testing.B) { + benchmarkServer(xtls.Enforced, false, b) +} + +func BenchmarkMTLSServer(b *testing.B) { + benchmarkServer(xtls.Enforced, true, b) +} + +func benchmarkKeepAliveServer(tlsMode xtls.ServerMode, mTLSEnabled bool, b *testing.B) { + handler := newMockKeepAliveHandler() + server := testBenchServer(testBenchListenAddress, handler, tlsMode, mTLSEnabled) + b.Cleanup(func() { server.Close(); handler.Close() }) + err := server.ListenAndServe() + require.NoError(b, err) + cert, err := tls.LoadX509KeyPair("./testdata/client.crt", "./testdata/client.key") + require.NoError(b, err) + conn, err := dial(server.listener.Addr().String(), tlsMode, []tls.Certificate{cert}) + require.NoError(b, err) + for n := 0; n < b.N; n++ { + msg := fmt.Sprintf("msg%d", n) + _, err = conn.Write([]byte(msg)) + require.NoError(b, err) + } +} + +func BenchmarkKeepAlivePlainTCPServer(b *testing.B) { + benchmarkKeepAliveServer(xtls.Disabled, false, b) +} + +func BenchmarkKeepAliveTLSServer(b *testing.B) { + benchmarkKeepAliveServer(xtls.Enforced, false, b) +} + +func BenchmarkKeepAliveMTLSServer(b *testing.B) { + benchmarkKeepAliveServer(xtls.Enforced, true, b) +} diff --git a/src/x/server/server_test.go b/src/x/server/server_test.go index c18cc0981f..40b2deb162 100644 --- a/src/x/server/server_test.go +++ b/src/x/server/server_test.go @@ -21,32 +21,82 @@ package server import ( + "crypto/tls" "fmt" "net" "sort" "sync" "sync/atomic" + "syscall" "testing" "time" "github.com/stretchr/testify/require" "github.com/m3db/m3/src/x/retry" + xtls "github.com/m3db/m3/src/x/tls" ) const ( testListenAddress = "127.0.0.1:0" ) +func isKeepAlive(conn net.Conn) (bool, error) { + tcpConn, ok := conn.(*net.TCPConn) + if !ok { + return false, nil + } + file, err := tcpConn.File() + if err != nil { + return false, err + } + defer file.Close() // nolint: errcheck,gosec + + fd := int(file.Fd()) + + value, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_KEEPALIVE) + if err != nil { + return false, err + } + if value == 1 { + return true, nil + } + return false, nil +} + +func waitFor(checkFn func() bool) { + numChecks := 5 + waitTime := 100 * time.Millisecond + checks := 0 + for !checkFn() && checks < numChecks { + time.Sleep(waitTime) + checks++ + } +} + +func testPlainTCPServer(addr string) (*server, *mockHandler, *int32, *int32) { + return testServer(addr, xtls.Disabled, false) +} + // nolint: unparam -func testServer(addr string) (*server, *mockHandler, *int32, *int32) { +func testServer(addr string, tlsMode xtls.ServerMode, mTLSEnabled bool) (*server, *mockHandler, *int32, *int32) { var ( numAdded int32 numRemoved int32 ) - opts := NewOptions().SetRetryOptions(retry.NewOptions().SetMaxRetries(2)) - opts = opts.SetInstrumentOptions(opts.InstrumentOptions().SetReportInterval(time.Second)) + tlsOpts := xtls.NewOptions(). + SetServerMode(tlsMode). + SetMutualTLSEnabled(mTLSEnabled). + SetCAFile("./testdata/rootCA.crt"). + SetCertFile("./testdata/server.crt"). + SetKeyFile("./testdata/server.key") + + opts := NewOptions(). + SetRetryOptions(retry.NewOptions().SetMaxRetries(2)). + SetTCPConnectionKeepAlive(true). + SetTCPConnectionKeepAlivePeriod(time.Hour) + opts = opts.SetInstrumentOptions(opts.InstrumentOptions().SetReportInterval(time.Second)).SetTLSOptions(tlsOpts) h := newMockHandler() s := NewServer(addr, h, opts).(*server) @@ -66,7 +116,7 @@ func testServer(addr string) (*server, *mockHandler, *int32, *int32) { } func TestServerListenAndClose(t *testing.T) { - s, h, numAdded, numRemoved := testServer(testListenAddress) + s, h, numAdded, numRemoved := testPlainTCPServer(testListenAddress) var ( numClients = 9 @@ -91,6 +141,7 @@ func TestServerListenAndClose(t *testing.T) { for h.called() < numClients { time.Sleep(100 * time.Millisecond) } + waitFor(func() bool { return h.called() == numClients }) require.False(t, h.isClosed()) @@ -104,7 +155,7 @@ func TestServerListenAndClose(t *testing.T) { } func TestServe(t *testing.T) { - s, _, _, _ := testServer(testListenAddress) + s, _, _, _ := testPlainTCPServer(testListenAddress) l, err := net.Listen("tcp", testListenAddress) require.NoError(t, err) @@ -117,6 +168,110 @@ func TestServe(t *testing.T) { s.Close() } +func TestTLS(t *testing.T) { + tests := []struct { + name string + tlsMode xtls.ServerMode + numClients int + expectedServerCalls int + dialFn func(i int, listenAddr string) (net.Conn, error) + appendExpectedResultFn func(expecteResult []string, i int, msg string) []string + }{ + { + name: "TLS permissive mode", + tlsMode: xtls.Permissive, + numClients: 9, + expectedServerCalls: 9, + dialFn: func(i int, listenAddr string) (net.Conn, error) { + if i%2 == 0 { + return net.Dial("tcp", listenAddr) + } + return tls.Dial("tcp", listenAddr, &tls.Config{InsecureSkipVerify: true}) // #nosec G402 + }, + appendExpectedResultFn: func(expectedResult []string, i int, msg string) []string { + return append(expectedResult, msg) + }, + }, + { + name: "TLS enforced mode", + tlsMode: xtls.Enforced, + numClients: 10, + expectedServerCalls: 5, + dialFn: func(i int, listenAddr string) (net.Conn, error) { + if i%2 == 0 { + return net.Dial("tcp", listenAddr) + } + return tls.Dial("tcp", listenAddr, &tls.Config{InsecureSkipVerify: true}) // #nosec G402 + }, + appendExpectedResultFn: func(expectedResult []string, i int, msg string) []string { + if i%2 == 1 { + return append(expectedResult, msg) + } + return expectedResult + }, + }, + { + name: "Mutual TLS", + tlsMode: xtls.Enforced, + numClients: 9, + expectedServerCalls: 9, + dialFn: func(i int, listenAddr string) (net.Conn, error) { + cert, err := tls.LoadX509KeyPair("./testdata/client.crt", "./testdata/client.key") + require.NoError(t, err) + // #nosec G402 + return tls.Dial( + "tcp", + listenAddr, + &tls.Config{InsecureSkipVerify: true, Certificates: []tls.Certificate{cert}}, + ) + }, + appendExpectedResultFn: func(expecteResult []string, i int, msg string) []string { + return append(expecteResult, msg) + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + s, h, numAdded, numRemoved := testServer(testListenAddress, tt.tlsMode, false) + var expectedRes []string + err := s.ListenAndServe() + require.NoError(t, err) + listenAddr := s.listener.Addr().String() + for i := 0; i < tt.numClients; i++ { + conn, err := tt.dialFn(i, listenAddr) + require.NoError(t, err) + waitFor(func() bool { + s.Lock() + defer s.Unlock() + return len(s.conns) == 1 + }) + s.Lock() + c := s.conns[0] + s.Unlock() + keepAlive, err := isKeepAlive(c.(*securedConn).Conn) + require.True(t, keepAlive) + require.NoError(t, err) + + msg := fmt.Sprintf("msg%d", i) + expectedRes = tt.appendExpectedResultFn(expectedRes, i, msg) + + _, err = conn.Write([]byte(msg)) + require.NoError(t, err) + } + waitFor(func() bool { return h.called() == tt.expectedServerCalls }) + require.False(t, h.isClosed()) + + s.Close() + + require.True(t, h.isClosed()) + require.Equal(t, int32(tt.numClients), atomic.LoadInt32(numAdded)) + require.Equal(t, int32(tt.numClients), atomic.LoadInt32(numRemoved)) + require.Equal(t, tt.expectedServerCalls, h.called()) + require.Equal(t, expectedRes, h.res()) + }) + } +} + type mockHandler struct { sync.Mutex diff --git a/src/x/server/testdata/client.crt b/src/x/server/testdata/client.crt new file mode 100644 index 0000000000..366d36e8b8 --- /dev/null +++ b/src/x/server/testdata/client.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKwMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDE1MTQ0NjEyWhcNMzQwNDEzMTQ0NjEyWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0Q2xpZW50Q2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMXgv5tgZD5iBW3LfTC27jPHTLlsAw8ZlBFLmd7potdS3Hy512Xa +kKOaKRKH/0Yla4BKGNh0ZP+xege15CpTFqwtzDPCRkaRvfSxRW19MukeergRDqqI +tEwURHQ5gfub7XB9e5wuNmW2GGs/xgiWsxxm6UMidXkhA5a2//OmDSm2JdcoxJbk +1m1OUzXUuLKLzCIRAbJFFIM1RpvjWSonxpJ2lsNCVQ+LdItxE9mAI+Y+PaDonyzg +ehfAqkl+wxGRevaDFFJr1defxmnJhaYv40AcEpXOWeetZFlspEtrPF7b/3GifRdx +Mlwq27wn3mk4wF1wa12kRNKiz+HHjZrB7UECAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAKblZ+C4Pdv5grKiCQcfFt53SbIys8uavxmzJNn9nq1QVgBmR/hX8yG8ULeh5 +kKrccwe2WXCBWsIj9cvGCzR+MPMvuIXd3d58yLybmIUBDfJSBC0v5TaNWP2ZdJDG +fH38DfQTeE9XcjpRtCGYtYwReBptSlNiYQlvwkukTNz3mV+0BpvQu/9/R0BNCJAF +9VeLgUcG7T7HC+foCeU7h83Y0xH0OALe33ntcrMPHUclXqImCiC7dl8pb3H7o/xJ +jS3KZxfv6ioxpirvpEbX/EeF+H1HQZUZ8y3+J3K37jMgzNamd/xsaJxEfCq4SE+n +HmgeXgqoNjrM6J0jYAfx1MjbIA== +-----END CERTIFICATE----- diff --git a/src/x/server/testdata/client.key b/src/x/server/testdata/client.key new file mode 100644 index 0000000000..ff226a549e --- /dev/null +++ b/src/x/server/testdata/client.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAxeC/m2BkPmIFbct9MLbuM8dMuWwDDxmUEUuZ3umi11LcfLnX +ZdqQo5opEof/RiVrgEoY2HRk/7F6B7XkKlMWrC3MM8JGRpG99LFFbX0y6R56uBEO +qoi0TBREdDmB+5vtcH17nC42ZbYYaz/GCJazHGbpQyJ1eSEDlrb/86YNKbYl1yjE +luTWbU5TNdS4sovMIhEBskUUgzVGm+NZKifGknaWw0JVD4t0i3ET2YAj5j49oOif +LOB6F8CqSX7DEZF69oMUUmvV15/GacmFpi/jQBwSlc5Z561kWWykS2s8Xtv/caJ9 +F3EyXCrbvCfeaTjAXXBrXaRE0qLP4ceNmsHtQQIDAQABAoIBAAtyP8MuJT5SjzvV +rI0317mZCsAjFl42PZFujR0O6MOJ4IU6ftI+fWVpUnzm7wZQvdIy9xL2UK1Vx9hQ +Vj14hvQ4xfosf8IvRgy0gG6f8mT3xWOGYRHOTJemCHuso+85CtgZ+h+DsNPbX7g8 +fSkcBopbDZ07jg4OsdVzCoU+kr5Z1/VDe0O0rTDzGVP686tN9I+DmDYv2HlBUaSZ +SyNZrLJFziqFx4ATTIE3aTLd29pzTl09WiCcZSxhlS+ROnN2iW2xDLNgY9SdUo2a +3r8N+xhYDNQz2paxlsv2x/tCRJvrQDoX7S5QZw4P1pVr6wo6xC+BU2UlpVHifo4M +egyDbAECgYEA5Y9+JDOSQF2cvIV4xTMgVnkLig/ngYesS44xz8HYTRR5s1WW6oCX +3j+OosZBbJpXeBlTK31G9Z92+Om2/Cj2uDvff7EJdhZ5sAdMtUy5X7p9rGTEyi5F +ecZDBfmwHH0lFdwpNtlky1uoaUsVd4IS28WMHoHYDGW241IW8xOlmeECgYEA3Ksb +KR7JCbHx21wh5oPVn5BLpdag7D+M1jBFtjPrzLDNhXlsV9zkjn7xETvTXWU2vNlh +OkoRu4VFEAnsgtxh+gT0lo9ZG9IWi2qP99vPpB3AFYuujBu3GA02/iOo5pri/6R0 +ZNEEreAaaOcT2z6K9HG0KMQ6QpYgXSlzJW8sf2ECgYEAvn2fMA03bIAB4xJi0EkH +qZoScEOYWQ0rdRsOzJbPlc7K2nzImdmRrFRTWVFo0uUUdk2VjX4Mlx/3ir/uHzsi +2GieowhWkI4/9kloZv2+yegoBxkrj5ZsAov57AhxEoLqdkRWUvR8xp9Nleo/awcd +/Q7loh8fF9KDvAjPkHAaOCECgYA98bZNI7wxgYcwGbvWdrmX8iyaIBbKWsiRM7nN +/OM7cYIv7rbwLyzlp1LKkK2zsP7donP9pd82caHCb9a5oV3LjmqOfSz5d08m0cIa +RNUT79oE8lIMOJd8I/GFA8OdAGuqcaLOzjHvEVK4ke1sBTGCjwyQyQzFtljdbg5J +utyV4QKBgHkSBKwrgaump08MJFfK9uFo5sr5jy692XYWMlBVhc+piPwUsoztjI1O +8cvie0mMj6oofSoSyD4FUg9kFUCRMPC0kga/zlHyL1Y/uILX4XUFMl4P/gMbC7kw +0d6BdEsp5SU8Onc9eV/EW+q2Mz7/t5ZHkUrdDQYGgkIBqRZcXlka +-----END RSA PRIVATE KEY----- diff --git a/src/x/server/testdata/rootCA.crt b/src/x/server/testdata/rootCA.crt new file mode 100644 index 0000000000..5d92901e85 --- /dev/null +++ b/src/x/server/testdata/rootCA.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDlTCCAn2gAwIBAgIUUwZEZu6XUKW03HPLHGDlt/d/BeUwDQYJKoZIhvcNAQEL +BQAwWjELMAkGA1UEBhMCTkwxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM +GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDETMBEGA1UEAwwKVGVzdFJvb3RDQTAe +Fw0yNDA0MDQxMTAwMzhaFw0zNDAxMDIxMTAwMzhaMFoxCzAJBgNVBAYTAk5MMRMw +EQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0 +eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IB +DwAwggEKAoIBAQDRwn7QLv6zup98kB9VJ1HvHQQMZxt2tqhpFGEc0bXiGrmhFKjV +XZ264ALnUJHQgM8rkRe+dTXg8455FPTMoT+O/BC44pt8CaY1C5kQzVkLaMwaVVxV +vsBfdZ9LOKJQmUXIf1qV/UK3P55Oc3TvwSQW3tU0eMMEOnuNdA4lDdlQnExb7tIJ +4sl0i2w6rB7CoS/0gx2eUPLhPK+vcwCM3d81Odb3ULVznyJ5ZUd5mYY85XXVFQdW +QSyaEE0ekzbmXQzjiqNAt67WEmbadoKdeg8jPG5ka8qBZ21ZGjCHQ8AbzpOn+eDk +n50zwxncpSYVxTilV70PbFSY33PdmdS6XjEnAgMBAAGjUzBRMB0GA1UdDgQWBBSo +kzZddlleTXZYQsBc8efmsZj3WTAfBgNVHSMEGDAWgBSokzZddlleTXZYQsBc8efm +sZj3WTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQACb1ZT3WPK +XdO6jYbZuMN5BBET+mNKQOYrdehY5oiLgr5sDwTwfZvf+q6ic7MpDRBOWMSQuYwp +aUxK7qfw4nSSZb4zDPpzB6Tauh7M5TTPYQwzesn9/JaUBO4vCTwOFGuWxEbXe6PF +VuQOyGQCR9a7juNgoDLiRpWi+xXHz1kMNSeZqV8bn7eo/SIziuSW1JScse91Brt1 +Db9L8iSJpKLDLtwv8fU4/NSORc/672uy4OLxNppsTthh1rk94KbC/FYllC6e8N8n +0iffEn4xE/CECYmAhvplXXS20hCTGbs7tPJ3DGsRfHOyI9RXiayNZuq9GIYKv6eR ++h7NYwXua7oP +-----END CERTIFICATE----- diff --git a/src/x/server/testdata/rootCA.key b/src/x/server/testdata/rootCA.key new file mode 100644 index 0000000000..6d8a527ee2 --- /dev/null +++ b/src/x/server/testdata/rootCA.key @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDRwn7QLv6zup98 +kB9VJ1HvHQQMZxt2tqhpFGEc0bXiGrmhFKjVXZ264ALnUJHQgM8rkRe+dTXg8455 +FPTMoT+O/BC44pt8CaY1C5kQzVkLaMwaVVxVvsBfdZ9LOKJQmUXIf1qV/UK3P55O +c3TvwSQW3tU0eMMEOnuNdA4lDdlQnExb7tIJ4sl0i2w6rB7CoS/0gx2eUPLhPK+v +cwCM3d81Odb3ULVznyJ5ZUd5mYY85XXVFQdWQSyaEE0ekzbmXQzjiqNAt67WEmba +doKdeg8jPG5ka8qBZ21ZGjCHQ8AbzpOn+eDkn50zwxncpSYVxTilV70PbFSY33Pd +mdS6XjEnAgMBAAECggEAc7MZadGSMibNU9LXRYwDuKXDlufEkU2dRSC1lETkU0w0 +efJAUjhqCSsIzvh1BZIXM8u7UhWq22LcglMZ2ElVGpRU0PZFlzxnTjkQe+EYrZ9n +fVr6LF2kFMN3UvFxLo3snPQKTlM82oEf29v5c0mQk4DkDyZRQ2LSxapvJuclqD0e +iMEG4Yad7p86AmJm7vpe9yp0POhfF5k9ZOAmrov67LMrFPRpWjeRr2ISa2Y/pjYI +vMqL+jGQi72rasNC0JYKyqn+v+LzocZGL4rCQUIpNmKkw81VolwkU8mfe87kneqh +lBsUgmg9nGtNnoFoxxeBNCEQGXf9+BhdGujujycmUQKBgQDo+a+/l/hn3E+7Tqup +nTAuZn80ODpQkfQ/L+jm6G0ENkil62kdyQCUZFfZqpj9dQwSPzCCViXrZymTL35G +1pzZqejPYGANRmiMeQMOOvyUwfIaEz3U9aj7hWl1v4WLUPzEUTTOsG8DBsR/lHBg +ZC081DH8U3m9fYN+Rp6xdT8z+QKBgQDmfXNfrNjFyxnB+kTJovM0k0f6p5HLKjF3 +VNFz0vpNgsa7DHXrmHSBBENi9oj4z72UQo1Q3bTRezR2TLZpsUmkGNEL0PoXTE1U +YVEb/VStLIZeKFv83uD2qvaYw2gA5785SNeinpweNhTC55T0ixojUmVA9eeIT/ex +nBDDJBuWHwKBgDQ12JQIW6vy7I9edwwA5Q5Q/ArY2wC5ZNJQS1KMHfpGrAs68Yiy +RgX7YtCt8bFDbNwd+yIKal8R9Hg+uX7ok6gX8suenof7Em0ApZWn1HkF6dq8GyxB +jYgogtTXgfcRxEO+qyXy1j4IYzrwKir/6D9sknMoxeyYV0KSUvgT/YEJAoGAaCIb +gwlLcqlc/Md+Vn75VDKKXZNhiiGI8bnvW13hWi2Qbaemiwd482UisM5jec4Zf6dF +w1g3PkFkpWHpM/02IR5ZK/aBVw9RDKNfCr88h3TLTDT9wlRL3QXGnaQDFA2f1liz +m7P/IqMaZChOouFJsNWkC2JN9cbzSFoTNKbWk88CgYAS37I31C6xyWlucbztFpmv +YqEPYRpIbRnJUq+F2Kof8dfT44C3rYwRxPHp2LMgyuPxdE491+Gfwsiv587KuBOP +XpCLuWNN38UDndQ5Zcaxl025akSnAF28EitjZCWzw+8TO/52358vpypwSYkdvQNx +or4Q8/AazdVqWUDo3sARPQ== +-----END PRIVATE KEY----- diff --git a/src/x/server/testdata/server.crt b/src/x/server/testdata/server.crt new file mode 100644 index 0000000000..3618a40052 --- /dev/null +++ b/src/x/server/testdata/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKvMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDA0MTE0ODIwWhcNMzEwNzA3MTE0ODIwWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0U2VydmVyQ2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOoGyqgrsBCufd9UNt6wr9UJzPaVqdrr/Zble2UPMVZOynAT69W5 +gMdNvrIDU6l1EnRkLyxmP7+beYZvWyU+kN9LyP0pdcgSQdtyhbr1CkqMxhM1MA2B +OFvcEFN2rybaqcLDlq2WUQkmyuVQt7/weLU9jC596PtxUL1e0+wqIItISWpcmemf +cwH9TVlD8Yh6emf87v27afxc0bZCauPsrEPvOC00oU4epXnA7TeDs4RtfqVAuVHX +oEG3oNZ3NHXIIgSht3rNAEfOxBX+7KGvW4hW9ci6W758ECGzHFJThhzO3+w+0YPP +29m+v4CVRaXAGxkVRPiAUDpU8RSPteywTyMCAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAuQME+WSPw1KKK1jgh1wuiyM5hou1BeagfwOipM3CEblTiHfoERsd2f7Oi6e2 +zPACrvKjVP6bHKjZPEztFHGXDuLdBqK97Vd/o4IFl6p/8EuBeWV85o4Rk1PMhqBI +0jSxsXXhBLgSTkUBgSllwgAbAaxm+L3wBJiQOhh/wEdfNSJ/GeL5477J56Q12/hA +rRCghYRAVVpr44NfG/PbuuMklQHK2l+wkJFBnx+zAAtZF/nb5yCVSy8z/p2TT72a +STfegXYJtE3p4X2+78xajrI7x/PWhNlzJRn3txLW6461vYJIB3ZDVUA2LZtmi3nd +9BUuJVSvsoVwlvrUBF0S1P0vWg== +-----END CERTIFICATE----- diff --git a/src/x/server/testdata/server.key b/src/x/server/testdata/server.key new file mode 100644 index 0000000000..6b6e1cd4cc --- /dev/null +++ b/src/x/server/testdata/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6gbKqCuwEK5931Q23rCv1QnM9pWp2uv9luV7ZQ8xVk7KcBPr +1bmAx02+sgNTqXUSdGQvLGY/v5t5hm9bJT6Q30vI/Sl1yBJB23KFuvUKSozGEzUw +DYE4W9wQU3avJtqpwsOWrZZRCSbK5VC3v/B4tT2MLn3o+3FQvV7T7Cogi0hJalyZ +6Z9zAf1NWUPxiHp6Z/zu/btp/FzRtkJq4+ysQ+84LTShTh6lecDtN4OzhG1+pUC5 +UdegQbeg1nc0dcgiBKG3es0AR87EFf7soa9biFb1yLpbvnwQIbMcUlOGHM7f7D7R +g8/b2b6/gJVFpcAbGRVE+IBQOlTxFI+17LBPIwIDAQABAoIBAG7rZSYr/rkJWxN8 +wLzvlYctq/27ldPzQjBA+Ck/+CxmD8DrUD3uPEE6cgXBWefZWzPbQBiVJeXU9NZo +9RREATo0m0CSZAa90IUIVKX1+ji3XDJCZVIhSusO9JX6jK2nugpb1ZDUy/fAcS4b +AdxxR2TvlP98Ie77GD+JMHygwugAJPQ4AFZQjO2F/2k3crVt9Frt1VSvf72nkM/C +sDJy4kv+lDZsbeveNPzJo1honiKdCDWpg4duLiLkkG4YvftWHGzpXHoOWnuwnrT5 +jrQTaG10d1jMpnxOwDrxRsFYWtz4kbJm2VtRsgWRIy4A4Vv8VKfFJ0cSu0CChjTE +7yVrExECgYEA9orYMAuuu/c0HGVcXOsA9x6XHtBXr5wK1fI973sGUrtkshTlBgjG +Bc7H+AhVBVdqr2ipm9pFZYSV1OYB2+3AgMQjhoZANR9jr5bmRjJgWuBi5umrFKa4 +n0IQbkwSzjxKvkhpJK25BY2kpjh7/128Q80PMkKnm0LR2gyUWvLFuUUCgYEA8wEJ +PyxHBu93NnbwTP0SUEOYFa654ctiVLbicdcUsbo/Qo4gl/0AIpGmMdeKqx3FWn9I +kCH1xlvb7hp7FNFhDZIiw6CT8V2RQHXdEO9/S0jjOOBg29hxARIgoU+PjjaQG/H+ +SqlDR/ijXUghlllwtP6jCJqy29+Ukpx8WnIkiUcCgYEA4DNXcinNlyWw//m1StJz +KEdrShxRAWO3/uOyKywHjgjYA5I4y4oC+pkXBJofJyJd5HIbe5286hruXpNkMWo+ +W1sCq30+upB3COEDO+bn9PyFNFRevgL3deyQIJ2RpHbzyaLz6uUIkuJCTt6i9JF7 +eXTrUtuGrAzR5ab26tZHtLkCgYBZtW42diAafVjjMHFx2pNiMjH4/zH8G1tHruRV +5nLrUbh8139M3Mep+18BU3MLzMfIquic3Qygl9TPR9NcUhekLOjMk4FKP6KZy2k7 +EU9K+bN4kczwHC1LmeeV5Ys3kwkMZ//ajDYcYbvdef18RbSH/OEvEf0pkpaEsWRu +92hpjQKBgH6Y3dW0bQIhXikjboD00vPn0zPuh35I9OfrPHMqDJiz+xom7Sz9tlGk +NfFll5PTf0gZtU7160uzhCp/OqXT3IdVgVt/G5ey13oYP7pCb8MWiuyNW0yUgtTp +GM5GUnEW5zJ1t2qJOxwoq/FA7D5C7EGRM/QpoMD0tsJoJKYyWkJO +-----END RSA PRIVATE KEY----- diff --git a/src/x/tls/config_manager.go b/src/x/tls/config_manager.go new file mode 100644 index 0000000000..b9d318748f --- /dev/null +++ b/src/x/tls/config_manager.go @@ -0,0 +1,169 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tls + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "sync" + "time" + + "github.com/uber-go/tally" + "go.uber.org/zap" + + "github.com/m3db/m3/src/x/instrument" +) + +const getConfigMetricName = "get-tls-config" + +var sleepFn = time.Sleep + +// ConfigManager is a tls config manager capable of certificate rotation +type ConfigManager interface { + TLSConfig() (*tls.Config, error) + ServerMode() ServerMode + ClientEnabled() bool +} + +type configManager struct { + mu sync.RWMutex + + log *zap.Logger + metrics configManagerMetrics + options Options + certPool *x509.CertPool + tlsConfig *tls.Config +} + +type configManagerMetrics struct { + getTLSConfigSuccess tally.Counter + getTLSConfigErrors tally.Counter +} + +func newConfigManagerMetrics(scope tally.Scope) configManagerMetrics { + return configManagerMetrics{ + getTLSConfigSuccess: scope.Tagged(map[string]string{"success": "true"}).Counter(getConfigMetricName), + getTLSConfigErrors: scope.Tagged(map[string]string{"success": "false"}).Counter(getConfigMetricName), + } +} + +// NewConfigManager creates a new config manager +func NewConfigManager(opts Options, instrumentOpts instrument.Options) ConfigManager { + scope := instrumentOpts.MetricsScope() + c := &configManager{ + log: instrumentOpts.Logger(), + metrics: newConfigManagerMetrics(scope), + options: opts, + certPool: x509.NewCertPool(), + } + go c.updateCertificates() + return c +} + +func (c *configManager) updateCertificates() { + if c.options.CertificatesTTL() == 0 { + return + } + for { + tlsConfig, err := c.loadTLSConfig() + if err != nil { + c.metrics.getTLSConfigErrors.Inc(1) + c.log.Error("get tls config error", zap.Error(err)) + sleepFn(c.options.CertificatesTTL()) + continue + } + c.mu.Lock() + c.tlsConfig = tlsConfig + c.mu.Unlock() + c.metrics.getTLSConfigSuccess.Inc(1) + sleepFn(c.options.CertificatesTTL()) + } +} + +func (c *configManager) loadCertPool() (*x509.CertPool, error) { + if c.options.CAFile() == "" { + return c.certPool, nil + } + certs, err := os.ReadFile(c.options.CAFile()) + if err != nil { + return nil, fmt.Errorf("read bundle error: %w", err) + } + c.mu.Lock() + defer c.mu.Unlock() + if ok := c.certPool.AppendCertsFromPEM(certs); !ok { + return nil, fmt.Errorf("cannot append cert to cert pool") + } + return c.certPool, nil +} + +func (c *configManager) loadX509KeyPair() ([]tls.Certificate, error) { + if c.options.CertFile() == "" || c.options.KeyFile() == "" { + return []tls.Certificate{}, nil + } + cert, err := tls.LoadX509KeyPair(c.options.CertFile(), c.options.KeyFile()) + if err != nil { + return nil, fmt.Errorf("load x509 key pair error: %w", err) + } + return []tls.Certificate{cert}, nil +} + +func (c *configManager) loadTLSConfig() (*tls.Config, error) { + certPool, err := c.loadCertPool() + if err != nil { + return nil, fmt.Errorf("load cert pool failed: %w", err) + } + certificate, err := c.loadX509KeyPair() + if err != nil { + return nil, fmt.Errorf("load x509 key pair failed: %w", err) + } + clientAuthType := tls.NoClientCert + if c.options.MutualTLSEnabled() { + clientAuthType = tls.RequireAndVerifyClientCert + } + // #nosec G402 + return &tls.Config{ + RootCAs: certPool, + ClientCAs: certPool, + Certificates: certificate, + ClientAuth: clientAuthType, + InsecureSkipVerify: c.options.InsecureSkipVerify(), + ServerName: c.options.ServerName(), + }, nil +} + +func (c *configManager) TLSConfig() (*tls.Config, error) { + if c.options.CertificatesTTL() == 0 || c.tlsConfig == nil { + return c.loadTLSConfig() + } + c.mu.RLock() + defer c.mu.RUnlock() + return c.tlsConfig, nil +} + +func (c *configManager) ServerMode() ServerMode { + return c.options.ServerMode() +} + +func (c *configManager) ClientEnabled() bool { + return c.options.ClientEnabled() +} diff --git a/src/x/tls/config_manager_test.go b/src/x/tls/config_manager_test.go new file mode 100644 index 0000000000..795916de78 --- /dev/null +++ b/src/x/tls/config_manager_test.go @@ -0,0 +1,212 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tls + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "testing" + "time" + + "github.com/stretchr/testify/require" + "github.com/uber-go/tally" + "go.uber.org/zap" + + "github.com/m3db/m3/src/x/instrument" + "github.com/m3db/m3/src/x/tallytest" +) + +func appendCA(filename string, certPool *x509.CertPool) error { + certs, err := os.ReadFile(filename) // #nosec G304 + if err != nil { + return fmt.Errorf("read bundle error: %w", err) + } + if ok := certPool.AppendCertsFromPEM(certs); !ok { + return fmt.Errorf("cannot append cert to cert pool") + } + return nil +} + +func TestLoadCertPool(t *testing.T) { + opts := NewOptions() + cm := &configManager{ + options: opts, + certPool: x509.NewCertPool(), + } + expectedCertPool := x509.NewCertPool() + + opts = opts.SetCAFile("") + cm.options = opts + certPool, err := cm.loadCertPool() + require.NoError(t, err) + require.Equal(t, expectedCertPool.Subjects(), certPool.Subjects()) + + opts = opts.SetCAFile("testdata/1.crt") + cm.options = opts + certPool, err = cm.loadCertPool() + require.NoError(t, err) + err = appendCA("testdata/1.crt", expectedCertPool) + require.NoError(t, err) + require.Equal(t, expectedCertPool.Subjects(), certPool.Subjects()) + require.Equal(t, expectedCertPool.Subjects(), cm.certPool.Subjects()) + + opts = opts.SetCAFile("testdata/2.crt") + cm.options = opts + certPool, err = cm.loadCertPool() + require.NoError(t, err) + err = appendCA("testdata/2.crt", expectedCertPool) + require.NoError(t, err) + require.Equal(t, expectedCertPool.Subjects(), certPool.Subjects()) + require.Equal(t, expectedCertPool.Subjects(), cm.certPool.Subjects()) + + opts = opts.SetCAFile("testdata/3.crt") + cm.options = opts + _, err = cm.loadCertPool() + require.Error(t, err) + require.Equal(t, expectedCertPool.Subjects(), cm.certPool.Subjects()) + + opts = opts.SetCAFile("wrong/path") + cm.options = opts + _, err = cm.loadCertPool() + require.Error(t, err) +} + +func TestLoadX509KeyPair(t *testing.T) { + opts := NewOptions() + cm := &configManager{ + options: opts, + } + + opts = opts.SetCertFile("").SetKeyFile("not empty") + cm.options = opts + certificates, err := cm.loadX509KeyPair() + require.NoError(t, err) + require.Len(t, certificates, 0) + + opts = opts.SetCertFile("not empty").SetKeyFile("") + cm.options = opts + certificates, err = cm.loadX509KeyPair() + require.NoError(t, err) + require.Len(t, certificates, 0) + + opts = opts.SetCertFile("wrong/path").SetKeyFile("wrong/path") + cm.options = opts + certificates, err = cm.loadX509KeyPair() + require.Error(t, err) + require.Len(t, certificates, 0) + + opts = opts.SetCertFile("testdata/1.crt").SetKeyFile("testdata/1.key") + cm.options = opts + certificates, err = cm.loadX509KeyPair() + require.NoError(t, err) + require.Len(t, certificates, 1) +} + +func TestLoadTLSConfig(t *testing.T) { + opts := NewOptions() + cm := &configManager{ + options: opts, + certPool: x509.NewCertPool(), + log: zap.NewNop(), + } + + opts = opts.SetCAFile("wrong/path") + cm.options = opts + _, err := cm.loadTLSConfig() + require.Error(t, err) + + opts = opts.SetCAFile("testdata/1.crt") + opts = opts.SetCertFile("wrong/path").SetKeyFile("wrong/path") + cm.options = opts + _, err = cm.loadTLSConfig() + require.Error(t, err) + + opts = opts. + SetCertFile("testdata/1.crt"). + SetKeyFile("testdata/1.key"). + SetMutualTLSEnabled(true). + SetInsecureSkipVerify(true). + SetServerName("server name") + cm.options = opts + tlsConfig, err := cm.loadTLSConfig() + require.NoError(t, err) + require.NotNil(t, tlsConfig.RootCAs) + require.NotNil(t, tlsConfig.ClientCAs) + require.Len(t, tlsConfig.Certificates, 1) + require.Equal(t, tls.RequireAndVerifyClientCert, tlsConfig.ClientAuth) + require.True(t, tlsConfig.InsecureSkipVerify) + require.Equal(t, "server name", tlsConfig.ServerName) +} + +func TestUpdateCertificate(t *testing.T) { + var waitCh = make(chan bool) + var waitCalledCh = make(chan bool) + const successMetricName = "success" + const errorMetricName = "error" + testScope := tally.NewTestScope("", map[string]string{}) + cmm := configManagerMetrics{ + getTLSConfigSuccess: testScope.Counter(successMetricName), + getTLSConfigErrors: testScope.Counter(errorMetricName), + } + originalSleepFn := sleepFn + sleepFn = func(d time.Duration) { + waitCalledCh <- true + <-waitCh + } + defer func() { sleepFn = originalSleepFn }() + + instrumentOpts := instrument.NewOptions().SetLogger(zap.NewNop()) + opts := NewOptions(). + SetCertificatesTTL(0) + cmInterface := NewConfigManager(opts, instrumentOpts) + cm := cmInterface.(*configManager) + cm.metrics = cmm + require.Nil(t, cm.tlsConfig) + tallytest.AssertCounterValue(t, 0, testScope.Snapshot(), successMetricName, map[string]string{}) + tallytest.AssertCounterValue(t, 0, testScope.Snapshot(), errorMetricName, map[string]string{}) + + opts = opts. + SetCertificatesTTL(time.Second). + SetCAFile("testdata/1.crt"). + SetCertFile("testdata/1.crt"). + SetKeyFile("testdata/1.key"). + SetMutualTLSEnabled(true). + SetInsecureSkipVerify(true). + SetServerName("server name") + cmInterface = NewConfigManager(opts, instrumentOpts) + cm = cmInterface.(*configManager) + cm.mu.Lock() + cm.metrics = cmm + cm.mu.Unlock() + <-waitCalledCh + require.NotNil(t, cm.tlsConfig) + tallytest.AssertCounterValue(t, 1, testScope.Snapshot(), successMetricName, map[string]string{}) + tallytest.AssertCounterValue(t, 0, testScope.Snapshot(), errorMetricName, map[string]string{}) + + opts = opts.SetCAFile("wrong/path") + cm.options = opts + waitCh <- true + <-waitCalledCh + tallytest.AssertCounterValue(t, 1, testScope.Snapshot(), successMetricName, map[string]string{}) + tallytest.AssertCounterValue(t, 1, testScope.Snapshot(), errorMetricName, map[string]string{}) +} diff --git a/src/x/tls/mode.go b/src/x/tls/mode.go new file mode 100644 index 0000000000..9cf7d113a7 --- /dev/null +++ b/src/x/tls/mode.go @@ -0,0 +1,33 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tls + +// ServerMode represents the TLS mode +type ServerMode uint16 + +const ( + // Disabled allows plaintext connections only + Disabled ServerMode = iota + // Permissive allows both TLS and plaintext connections + Permissive + // Enforced allows TLS connections only + Enforced +) diff --git a/src/x/tls/mode_test.go b/src/x/tls/mode_test.go new file mode 100644 index 0000000000..620ef3b279 --- /dev/null +++ b/src/x/tls/mode_test.go @@ -0,0 +1,42 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tls + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestTLSModeUnmarshal(t *testing.T) { + var tlsMode ServerMode + + require.NoError(t, tlsMode.UnmarshalText([]byte("disabled"))) + require.Equal(t, Disabled, tlsMode) + + require.NoError(t, tlsMode.UnmarshalText([]byte("permissive"))) + require.Equal(t, Permissive, tlsMode) + + require.NoError(t, tlsMode.UnmarshalText([]byte("enforced"))) + require.Equal(t, Enforced, tlsMode) + + require.Error(t, tlsMode.UnmarshalText([]byte("unknown"))) +} diff --git a/src/x/tls/options.go b/src/x/tls/options.go new file mode 100644 index 0000000000..9d30ec61a0 --- /dev/null +++ b/src/x/tls/options.go @@ -0,0 +1,183 @@ +// Copyright (c) 2017 Uber Technologies, Inc. +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +package tls + +import "time" + +// Options provide a set of TLS options +type Options interface { + // SetClientEnabled sets the TLS enabled option + SetClientEnabled(value bool) Options + // ClientEnabled returns the TLS enabled option + ClientEnabled() bool + + // SetInsecureSkipVerify sets the insecure skip verify option + SetInsecureSkipVerify(value bool) Options + // InsecureSkipVerify returns the insecure skip verify option + InsecureSkipVerify() bool + + // SetServerName sets the server name option + SetServerName(value string) Options + // ServerName returns the server name option + ServerName() string + + // SetServerMode sets the tls mode + SetServerMode(value ServerMode) Options + // Mode returns the tls mode + ServerMode() ServerMode + + // SetMutualTLSEnabled sets the mutual tls enabled option + SetMutualTLSEnabled(value bool) Options + // MutualTLSEnabled returns the mutual tls enabled option + MutualTLSEnabled() bool + + // SetCertFile sets the certificate file path + SetCertFile(value string) Options + // CertFile returns the certificate file path + CertFile() string + + // SetKeyFile sets the private key file path + SetKeyFile(value string) Options + // KeyFile returns the private key file path + KeyFile() string + + // SetCAFile sets the CA file path + SetCAFile(value string) Options + // CAFile returns the CA file path + CAFile() string + + // SetCertificatesTTL sets the certificates TTL + SetCertificatesTTL(value time.Duration) Options + // CertificatesTTL returns the certificates TTL + CertificatesTTL() time.Duration +} + +type options struct { + serverName string + certFile string + keyFile string + caFile string + certificatesTTL time.Duration + serverMode ServerMode + clientEnabled bool + insecureSkipVerify bool + mTLSEnabled bool +} + +// NewOptions creates a new set of tls options +func NewOptions() Options { + return &options{ + clientEnabled: false, + insecureSkipVerify: true, + serverMode: Disabled, + mTLSEnabled: false, + } +} + +func (o *options) SetClientEnabled(value bool) Options { + opts := *o + opts.clientEnabled = value + return &opts +} + +func (o *options) ClientEnabled() bool { + return o.clientEnabled +} + +func (o *options) SetInsecureSkipVerify(value bool) Options { + opts := *o + opts.insecureSkipVerify = value + return &opts +} + +func (o *options) InsecureSkipVerify() bool { + return o.insecureSkipVerify +} + +func (o *options) SetServerName(value string) Options { + opts := *o + opts.serverName = value + return &opts +} + +func (o *options) ServerName() string { + return o.serverName +} + +func (o *options) SetServerMode(value ServerMode) Options { + opts := *o + opts.serverMode = value + return &opts +} + +func (o *options) ServerMode() ServerMode { + return o.serverMode +} + +func (o *options) SetMutualTLSEnabled(value bool) Options { + opts := *o + opts.mTLSEnabled = value + return &opts +} + +func (o *options) MutualTLSEnabled() bool { + return o.mTLSEnabled +} + +func (o *options) SetCertFile(value string) Options { + opts := *o + opts.certFile = value + return &opts +} + +func (o *options) CertFile() string { + return o.certFile +} + +func (o *options) SetKeyFile(value string) Options { + opts := *o + opts.keyFile = value + return &opts +} + +func (o *options) KeyFile() string { + return o.keyFile +} + +func (o *options) SetCAFile(value string) Options { + opts := *o + opts.caFile = value + return &opts +} + +func (o *options) CAFile() string { + return o.caFile +} + +func (o *options) SetCertificatesTTL(value time.Duration) Options { + opts := *o + opts.certificatesTTL = value + return &opts +} + +func (o *options) CertificatesTTL() time.Duration { + return o.certificatesTTL +} diff --git a/src/x/tls/servermode_enumer.go b/src/x/tls/servermode_enumer.go new file mode 100644 index 0000000000..64ac5918b5 --- /dev/null +++ b/src/x/tls/servermode_enumer.go @@ -0,0 +1,94 @@ +// Code generated by "enumer -type ServerMode -text"; DO NOT EDIT. + +package tls + +import ( + "fmt" + "strings" +) + +const _ServerModeName = "DisabledPermissiveEnforced" + +var _ServerModeIndex = [...]uint8{0, 8, 18, 26} + +const _ServerModeLowerName = "disabledpermissiveenforced" + +func (i ServerMode) String() string { + if i >= ServerMode(len(_ServerModeIndex)-1) { + return fmt.Sprintf("ServerMode(%d)", i) + } + return _ServerModeName[_ServerModeIndex[i]:_ServerModeIndex[i+1]] +} + +// An "invalid array index" compiler error signifies that the constant values have changed. +// Re-run the stringer command to generate them again. +func _ServerModeNoOp() { + var x [1]struct{} + _ = x[Disabled-(0)] + _ = x[Permissive-(1)] + _ = x[Enforced-(2)] +} + +var _ServerModeValues = []ServerMode{Disabled, Permissive, Enforced} + +var _ServerModeNameToValueMap = map[string]ServerMode{ + _ServerModeName[0:8]: Disabled, + _ServerModeLowerName[0:8]: Disabled, + _ServerModeName[8:18]: Permissive, + _ServerModeLowerName[8:18]: Permissive, + _ServerModeName[18:26]: Enforced, + _ServerModeLowerName[18:26]: Enforced, +} + +var _ServerModeNames = []string{ + _ServerModeName[0:8], + _ServerModeName[8:18], + _ServerModeName[18:26], +} + +// ServerModeString retrieves an enum value from the enum constants string name. +// Throws an error if the param is not part of the enum. +func ServerModeString(s string) (ServerMode, error) { + if val, ok := _ServerModeNameToValueMap[s]; ok { + return val, nil + } + + if val, ok := _ServerModeNameToValueMap[strings.ToLower(s)]; ok { + return val, nil + } + return 0, fmt.Errorf("%s does not belong to ServerMode values", s) +} + +// ServerModeValues returns all values of the enum +func ServerModeValues() []ServerMode { + return _ServerModeValues +} + +// ServerModeStrings returns a slice of all String values of the enum +func ServerModeStrings() []string { + strs := make([]string, len(_ServerModeNames)) + copy(strs, _ServerModeNames) + return strs +} + +// IsAServerMode returns "true" if the value is listed in the enum definition. "false" otherwise +func (i ServerMode) IsAServerMode() bool { + for _, v := range _ServerModeValues { + if i == v { + return true + } + } + return false +} + +// MarshalText implements the encoding.TextMarshaler interface for ServerMode +func (i ServerMode) MarshalText() ([]byte, error) { + return []byte(i.String()), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface for ServerMode +func (i *ServerMode) UnmarshalText(text []byte) error { + var err error + *i, err = ServerModeString(string(text)) + return err +} diff --git a/src/x/tls/testdata/1.crt b/src/x/tls/testdata/1.crt new file mode 100644 index 0000000000..3618a40052 --- /dev/null +++ b/src/x/tls/testdata/1.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKvMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDA0MTE0ODIwWhcNMzEwNzA3MTE0ODIwWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0U2VydmVyQ2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAOoGyqgrsBCufd9UNt6wr9UJzPaVqdrr/Zble2UPMVZOynAT69W5 +gMdNvrIDU6l1EnRkLyxmP7+beYZvWyU+kN9LyP0pdcgSQdtyhbr1CkqMxhM1MA2B +OFvcEFN2rybaqcLDlq2WUQkmyuVQt7/weLU9jC596PtxUL1e0+wqIItISWpcmemf +cwH9TVlD8Yh6emf87v27afxc0bZCauPsrEPvOC00oU4epXnA7TeDs4RtfqVAuVHX +oEG3oNZ3NHXIIgSht3rNAEfOxBX+7KGvW4hW9ci6W758ECGzHFJThhzO3+w+0YPP +29m+v4CVRaXAGxkVRPiAUDpU8RSPteywTyMCAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAuQME+WSPw1KKK1jgh1wuiyM5hou1BeagfwOipM3CEblTiHfoERsd2f7Oi6e2 +zPACrvKjVP6bHKjZPEztFHGXDuLdBqK97Vd/o4IFl6p/8EuBeWV85o4Rk1PMhqBI +0jSxsXXhBLgSTkUBgSllwgAbAaxm+L3wBJiQOhh/wEdfNSJ/GeL5477J56Q12/hA +rRCghYRAVVpr44NfG/PbuuMklQHK2l+wkJFBnx+zAAtZF/nb5yCVSy8z/p2TT72a +STfegXYJtE3p4X2+78xajrI7x/PWhNlzJRn3txLW6461vYJIB3ZDVUA2LZtmi3nd +9BUuJVSvsoVwlvrUBF0S1P0vWg== +-----END CERTIFICATE----- diff --git a/src/x/tls/testdata/1.key b/src/x/tls/testdata/1.key new file mode 100644 index 0000000000..6b6e1cd4cc --- /dev/null +++ b/src/x/tls/testdata/1.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA6gbKqCuwEK5931Q23rCv1QnM9pWp2uv9luV7ZQ8xVk7KcBPr +1bmAx02+sgNTqXUSdGQvLGY/v5t5hm9bJT6Q30vI/Sl1yBJB23KFuvUKSozGEzUw +DYE4W9wQU3avJtqpwsOWrZZRCSbK5VC3v/B4tT2MLn3o+3FQvV7T7Cogi0hJalyZ +6Z9zAf1NWUPxiHp6Z/zu/btp/FzRtkJq4+ysQ+84LTShTh6lecDtN4OzhG1+pUC5 +UdegQbeg1nc0dcgiBKG3es0AR87EFf7soa9biFb1yLpbvnwQIbMcUlOGHM7f7D7R +g8/b2b6/gJVFpcAbGRVE+IBQOlTxFI+17LBPIwIDAQABAoIBAG7rZSYr/rkJWxN8 +wLzvlYctq/27ldPzQjBA+Ck/+CxmD8DrUD3uPEE6cgXBWefZWzPbQBiVJeXU9NZo +9RREATo0m0CSZAa90IUIVKX1+ji3XDJCZVIhSusO9JX6jK2nugpb1ZDUy/fAcS4b +AdxxR2TvlP98Ie77GD+JMHygwugAJPQ4AFZQjO2F/2k3crVt9Frt1VSvf72nkM/C +sDJy4kv+lDZsbeveNPzJo1honiKdCDWpg4duLiLkkG4YvftWHGzpXHoOWnuwnrT5 +jrQTaG10d1jMpnxOwDrxRsFYWtz4kbJm2VtRsgWRIy4A4Vv8VKfFJ0cSu0CChjTE +7yVrExECgYEA9orYMAuuu/c0HGVcXOsA9x6XHtBXr5wK1fI973sGUrtkshTlBgjG +Bc7H+AhVBVdqr2ipm9pFZYSV1OYB2+3AgMQjhoZANR9jr5bmRjJgWuBi5umrFKa4 +n0IQbkwSzjxKvkhpJK25BY2kpjh7/128Q80PMkKnm0LR2gyUWvLFuUUCgYEA8wEJ +PyxHBu93NnbwTP0SUEOYFa654ctiVLbicdcUsbo/Qo4gl/0AIpGmMdeKqx3FWn9I +kCH1xlvb7hp7FNFhDZIiw6CT8V2RQHXdEO9/S0jjOOBg29hxARIgoU+PjjaQG/H+ +SqlDR/ijXUghlllwtP6jCJqy29+Ukpx8WnIkiUcCgYEA4DNXcinNlyWw//m1StJz +KEdrShxRAWO3/uOyKywHjgjYA5I4y4oC+pkXBJofJyJd5HIbe5286hruXpNkMWo+ +W1sCq30+upB3COEDO+bn9PyFNFRevgL3deyQIJ2RpHbzyaLz6uUIkuJCTt6i9JF7 +eXTrUtuGrAzR5ab26tZHtLkCgYBZtW42diAafVjjMHFx2pNiMjH4/zH8G1tHruRV +5nLrUbh8139M3Mep+18BU3MLzMfIquic3Qygl9TPR9NcUhekLOjMk4FKP6KZy2k7 +EU9K+bN4kczwHC1LmeeV5Ys3kwkMZ//ajDYcYbvdef18RbSH/OEvEf0pkpaEsWRu +92hpjQKBgH6Y3dW0bQIhXikjboD00vPn0zPuh35I9OfrPHMqDJiz+xom7Sz9tlGk +NfFll5PTf0gZtU7160uzhCp/OqXT3IdVgVt/G5ey13oYP7pCb8MWiuyNW0yUgtTp +GM5GUnEW5zJ1t2qJOxwoq/FA7D5C7EGRM/QpoMD0tsJoJKYyWkJO +-----END RSA PRIVATE KEY----- diff --git a/src/x/tls/testdata/2.crt b/src/x/tls/testdata/2.crt new file mode 100644 index 0000000000..366d36e8b8 --- /dev/null +++ b/src/x/tls/testdata/2.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDPzCCAicCFFJVDW2T6lamcRZYStsBCQTKicKwMA0GCSqGSIb3DQEBCwUAMFox +CzAJBgNVBAYTAk5MMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRl +cm5ldCBXaWRnaXRzIFB0eSBMdGQxEzARBgNVBAMMClRlc3RSb290Q0EwHhcNMjQw +NDE1MTQ0NjEyWhcNMzQwNDEzMTQ0NjEyWjBeMQswCQYDVQQGEwJOTDETMBEGA1UE +CAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk +MRcwFQYDVQQDDA5UZXN0Q2xpZW50Q2VydDCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAMXgv5tgZD5iBW3LfTC27jPHTLlsAw8ZlBFLmd7potdS3Hy512Xa +kKOaKRKH/0Yla4BKGNh0ZP+xege15CpTFqwtzDPCRkaRvfSxRW19MukeergRDqqI +tEwURHQ5gfub7XB9e5wuNmW2GGs/xgiWsxxm6UMidXkhA5a2//OmDSm2JdcoxJbk +1m1OUzXUuLKLzCIRAbJFFIM1RpvjWSonxpJ2lsNCVQ+LdItxE9mAI+Y+PaDonyzg +ehfAqkl+wxGRevaDFFJr1defxmnJhaYv40AcEpXOWeetZFlspEtrPF7b/3GifRdx +Mlwq27wn3mk4wF1wa12kRNKiz+HHjZrB7UECAwEAATANBgkqhkiG9w0BAQsFAAOC +AQEAKblZ+C4Pdv5grKiCQcfFt53SbIys8uavxmzJNn9nq1QVgBmR/hX8yG8ULeh5 +kKrccwe2WXCBWsIj9cvGCzR+MPMvuIXd3d58yLybmIUBDfJSBC0v5TaNWP2ZdJDG +fH38DfQTeE9XcjpRtCGYtYwReBptSlNiYQlvwkukTNz3mV+0BpvQu/9/R0BNCJAF +9VeLgUcG7T7HC+foCeU7h83Y0xH0OALe33ntcrMPHUclXqImCiC7dl8pb3H7o/xJ +jS3KZxfv6ioxpirvpEbX/EeF+H1HQZUZ8y3+J3K37jMgzNamd/xsaJxEfCq4SE+n +HmgeXgqoNjrM6J0jYAfx1MjbIA== +-----END CERTIFICATE----- diff --git a/src/x/tls/testdata/3.crt b/src/x/tls/testdata/3.crt new file mode 100644 index 0000000000..dd3d9a25e0 --- /dev/null +++ b/src/x/tls/testdata/3.crt @@ -0,0 +1 @@ +wrong data