Files
Shaunak KashyapandGitHub 778f7b8fe2 [fips140][configurablehttpprovider.TestFunctionalityDownloadFileHTTPS] Skip test if GODEBUG=fips140=only is set (#14076)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue.
Ex. Adding a feature - Explain what this achieves.-->
#### Description

When
https://github.com/open-telemetry/opentelemetry-collector/pull/13926 is
merged, all unit tests in this repository will be run with
`GODEBUG=fips140=only` to help surface any FIPS-140 violations in all
the Go modules in this repository.

One such unit test that fails in these circumstances is
`confmap/provider/internal/configurablehttpprovider.TestFunctionalityDownloadFileHTTPS`.
However, the FIPS violation surfaced by this test is from the test code
itself (as opposed to from OpenTelemetry Collector core code that the
test is exercising), specifically when this call is made:
https://github.com/open-telemetry/opentelemetry-collector/blob/e0b4b19597eff5783b52400e5f22d007edae0f00/confmap/provider/internal/configurablehttpprovider/provider_test.go#L81

As such, this is not a FIPS violation we need to worry about when
running the OpenTelemetry Collector. To prevent the unit test from
failing when it's run with `GODEBUG=fips140=only`, we skip it.

<!-- Issue number if applicable -->
#### Link to tracking issue
Fixes #13998

<!--Describe what testing was performed and which tests were added.-->
#### Testing

Run the `configurablehttpprovider.TestFunctionalityDownloadFileHTTPS`
unit test with `GODEBUG=fips140=only`.
   ```
   $ cd confmap/
$ GODEBUG=fips140=only go test
./provider/internal/configurablehttpprovider/... -test.v -test.run
TestFunctionalityDownloadFileHTTPS -count 1
   ```
2025-11-10 10:52:11 +00:00

322 lines
9.5 KiB
Go

// Copyright The OpenTelemetry Authors
// SPDX-License-Identifier: Apache-2.0
package configurablehttpprovider
import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"fmt"
"math/big"
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/confmap"
"go.opentelemetry.io/collector/confmap/confmaptest"
"go.opentelemetry.io/collector/internal/testutil"
)
func newConfigurableHTTPProvider(scheme SchemeType, set confmap.ProviderSettings) *provider {
return New(scheme, set).(*provider)
}
func answerGet(w http.ResponseWriter, _ *http.Request) {
f, err := os.ReadFile("./testdata/otel-config.yaml")
if err != nil {
w.WriteHeader(http.StatusNotFound)
_, innerErr := w.Write([]byte("Cannot find the config file"))
if innerErr != nil {
fmt.Println("Write failed: ", innerErr)
}
return
}
w.WriteHeader(http.StatusOK)
_, err = w.Write(f)
if err != nil {
fmt.Println("Write failed: ", err)
}
}
// Generate a self signed certificate specific for the tests. Based on
// https://go.dev/src/crypto/tls/generate_cert.go
func generateCertificate(t *testing.T, hostname string) (cert, key string, err error) {
testutil.SkipIfFIPSOnly(t, "x509.CreateCertificate uses SHA-1")
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return "", "", fmt.Errorf("Failed to generate private key: %w", err)
}
keyUsage := x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign
notBefore := time.Now()
notAfter := notBefore.Add(time.Hour * 12)
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return "", "", fmt.Errorf("Failed to generate serial number: %w", err)
}
template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"Httpprovider Co"},
},
NotBefore: notBefore,
NotAfter: notAfter,
KeyUsage: keyUsage,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
IsCA: true,
DNSNames: []string{hostname},
}
derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return "", "", fmt.Errorf("Failed to create certificate: %w", err)
}
tempDir := t.TempDir()
certOut, err := os.CreateTemp(tempDir, "cert*.pem")
if err != nil {
return "", "", fmt.Errorf("Failed to open cert.pem for writing: %w", err)
}
defer certOut.Close()
if err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil {
return "", "", fmt.Errorf("Failed to write data to cert.pem: %w", err)
}
keyOut, err := os.CreateTemp(tempDir, "key*.pem")
if err != nil {
return "", "", fmt.Errorf("Failed to open key.pem for writing: %w", err)
}
defer keyOut.Close()
privBytes, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return "", "", fmt.Errorf("Unable to marshal private key: %w", err)
}
if err := pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil {
return "", "", fmt.Errorf("Failed to write data to key.pem: %w", err)
}
return certOut.Name(), keyOut.Name(), nil
}
func TestFunctionalityDownloadFileHTTP(t *testing.T) {
fp := newConfigurableHTTPProvider(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(answerGet))
defer ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
assert.NoError(t, err)
assert.NoError(t, fp.Shutdown(context.Background()))
}
func TestFunctionalityDownloadFileHTTPS(t *testing.T) {
certPath, keyPath, err := generateCertificate(t, "localhost")
require.NoError(t, err)
invalidCert, err := os.CreateTemp(t.TempDir(), "cert*.crt")
defer func() { require.NoError(t, invalidCert.Close()) }()
require.NoError(t, err)
_, err = invalidCert.Write([]byte{0, 1, 2})
require.NoError(t, err)
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
require.NoError(t, err)
ts := httptest.NewUnstartedServer(http.HandlerFunc(answerGet))
ts.TLS = &tls.Config{Certificates: []tls.Certificate{cert}}
ts.StartTLS()
defer ts.Close()
tests := []struct {
name string
certPath string
hostName string
useCertificate bool
skipHostnameValidation bool
shouldError bool
}{
{
name: "Test valid certificate and name",
certPath: certPath,
hostName: "localhost",
useCertificate: true,
skipHostnameValidation: false,
shouldError: false,
},
{
name: "Test valid certificate with invalid name",
certPath: certPath,
hostName: "127.0.0.1",
useCertificate: true,
skipHostnameValidation: false,
shouldError: true,
},
{
name: "Test valid certificate with invalid name, skip validation",
certPath: certPath,
hostName: "127.0.0.1",
useCertificate: true,
skipHostnameValidation: true,
shouldError: false,
},
{
name: "Test no certificate should fail",
certPath: certPath,
hostName: "localhost",
useCertificate: false,
skipHostnameValidation: false,
shouldError: true,
},
{
name: "Test invalid cert",
certPath: invalidCert.Name(),
hostName: "localhost",
useCertificate: true,
skipHostnameValidation: false,
shouldError: true,
},
{
name: "Test no cert",
certPath: "no_certificate",
hostName: "localhost",
useCertificate: true,
skipHostnameValidation: false,
shouldError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
fp := newConfigurableHTTPProvider(HTTPSScheme, confmaptest.NewNopProviderSettings())
// Parse url of the test server to get the port number.
tsURL, err := url.Parse(ts.URL)
require.NoError(t, err)
if tt.useCertificate {
fp.caCertPath = tt.certPath
}
fp.insecureSkipVerify = tt.skipHostnameValidation
_, err = fp.Retrieve(context.Background(), fmt.Sprintf("https://%s:%s", tt.hostName, tsURL.Port()), nil)
if tt.shouldError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
func TestUnsupportedScheme(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
_, err := fp.Retrieve(context.Background(), "https://...", nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
fp = New(HTTPSScheme, confmaptest.NewNopProviderSettings())
_, err = fp.Retrieve(context.Background(), "http://...", nil)
require.Error(t, err)
assert.NoError(t, fp.Shutdown(context.Background()))
}
func TestEmptyURI(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusBadRequest)
}))
defer ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestRetrieveFromShutdownServer(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(http.ResponseWriter, *http.Request) {}))
ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestNonExistent(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusNotFound)
}))
defer ts.Close()
_, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.Error(t, err)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestInvalidYAML(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
_, err := w.Write([]byte("wrong : ["))
if err != nil {
fmt.Println("Write failed: ", err)
}
}))
defer ts.Close()
ret, err := fp.Retrieve(context.Background(), ts.URL, nil)
require.NoError(t, err)
raw, err := ret.AsRaw()
require.NoError(t, err)
assert.Equal(t, "wrong : [", raw)
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestScheme(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
assert.Equal(t, "http", fp.Scheme())
require.NoError(t, fp.Shutdown(context.Background()))
}
func TestValidateProviderScheme(t *testing.T) {
assert.NoError(t, confmaptest.ValidateProviderScheme(New(HTTPScheme, confmaptest.NewNopProviderSettings())))
}
func TestInvalidURI(t *testing.T) {
fp := New(HTTPScheme, confmaptest.NewNopProviderSettings())
tests := []struct {
uri string
err string
}{
{
uri: "foo://..",
err: "uri is not supported by \"http\" provider",
},
{
uri: "http://",
err: "no Host in request URL",
},
{
uri: "http://{}",
err: "invalid character \"{\" in host name",
},
}
for _, tt := range tests {
t.Run(tt.uri, func(t *testing.T) {
_, err := fp.Retrieve(context.Background(), tt.uri, nil)
assert.ErrorContains(t, err, tt.err)
})
}
}