Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0889405d84 | |||
| edd160d112 | |||
| 09a833b317 | |||
| 206eb51961 |
@@ -1,91 +0,0 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
// TestAnyTypeMarshaling tests that google.protobuf.Any types are properly marshaled with @type field
|
||||
func TestAnyTypeMarshaling(t *testing.T) {
|
||||
marshaler := Marshaler{}
|
||||
|
||||
// Create a StringValue message
|
||||
stringValue := wrapperspb.String("test value")
|
||||
|
||||
// Wrap it in an Any message
|
||||
anyMsg, err := anypb.New(stringValue)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Any message: %v", err)
|
||||
}
|
||||
|
||||
// Marshal using our JSON marshaler
|
||||
data, err := marshaler.Marshal(anyMsg)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to marshal Any message: %v", err)
|
||||
}
|
||||
|
||||
// Unmarshal into a map to check for @type field
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// Check that @type field exists
|
||||
typeURL, ok := result["@type"].(string)
|
||||
if !ok {
|
||||
t.Fatalf("@type field not found in JSON output. Got: %v", string(data))
|
||||
}
|
||||
|
||||
// Verify the type URL is correct
|
||||
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
|
||||
if typeURL != expectedTypeURL {
|
||||
t.Errorf("Expected @type to be %s, got %s", expectedTypeURL, typeURL)
|
||||
}
|
||||
|
||||
// Verify the value field exists
|
||||
if _, ok := result["value"]; !ok {
|
||||
t.Errorf("value field not found in JSON output. Got: %v", string(data))
|
||||
}
|
||||
|
||||
t.Logf("Successfully marshaled Any type with @type field: %s", string(data))
|
||||
}
|
||||
|
||||
// TestAnyTypeUnmarshaling tests that JSON with @type field can be unmarshaled into google.protobuf.Any
|
||||
func TestAnyTypeUnmarshaling(t *testing.T) {
|
||||
marshaler := Marshaler{}
|
||||
|
||||
// JSON representation of an Any message with @type field
|
||||
jsonData := []byte(`{
|
||||
"@type": "type.googleapis.com/google.protobuf.StringValue",
|
||||
"value": "test value"
|
||||
}`)
|
||||
|
||||
// Unmarshal into an Any message
|
||||
anyMsg := &anypb.Any{}
|
||||
if err := marshaler.Unmarshal(jsonData, anyMsg); err != nil {
|
||||
t.Fatalf("Failed to unmarshal Any message: %v", err)
|
||||
}
|
||||
|
||||
// Verify the type URL is set
|
||||
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
|
||||
if anyMsg.TypeUrl != expectedTypeURL {
|
||||
t.Errorf("Expected TypeUrl to be %s, got %s", expectedTypeURL, anyMsg.TypeUrl)
|
||||
}
|
||||
|
||||
// Unmarshal the contained message
|
||||
stringValue := &wrapperspb.StringValue{}
|
||||
if err := anyMsg.UnmarshalTo(stringValue); err != nil {
|
||||
t.Fatalf("Failed to unmarshal contained message: %v", err)
|
||||
}
|
||||
|
||||
// Verify the value
|
||||
expectedValue := "test value"
|
||||
if stringValue.Value != expectedValue {
|
||||
t.Errorf("Expected value to be %s, got %s", expectedValue, stringValue.Value)
|
||||
}
|
||||
|
||||
t.Logf("Successfully unmarshaled Any type from JSON with @type field")
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"go-micro.dev/v5/codec"
|
||||
"google.golang.org/protobuf/types/known/anypb"
|
||||
"google.golang.org/protobuf/types/known/wrapperspb"
|
||||
)
|
||||
|
||||
// mockReadWriteCloser implements io.ReadWriteCloser for testing
|
||||
type mockReadWriteCloser struct {
|
||||
*bytes.Buffer
|
||||
}
|
||||
|
||||
func (m *mockReadWriteCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TestCodecAnyTypeWrite tests that google.protobuf.Any types are properly written with @type field
|
||||
func TestCodecAnyTypeWrite(t *testing.T) {
|
||||
buf := &mockReadWriteCloser{Buffer: bytes.NewBuffer(nil)}
|
||||
c := NewCodec(buf).(*Codec)
|
||||
|
||||
// Create a StringValue message
|
||||
stringValue := wrapperspb.String("test value")
|
||||
|
||||
// Wrap it in an Any message
|
||||
anyMsg, err := anypb.New(stringValue)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create Any message: %v", err)
|
||||
}
|
||||
|
||||
// Write the message
|
||||
msg := &codec.Message{
|
||||
Type: codec.Response,
|
||||
}
|
||||
if err := c.Write(msg, anyMsg); err != nil {
|
||||
t.Fatalf("Failed to write Any message: %v", err)
|
||||
}
|
||||
|
||||
// Parse the written JSON
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(buf.Bytes(), &result); err != nil {
|
||||
t.Fatalf("Failed to unmarshal JSON: %v", err)
|
||||
}
|
||||
|
||||
// Check that @type field exists
|
||||
typeURL, ok := result["@type"].(string)
|
||||
if !ok {
|
||||
t.Fatalf("@type field not found in JSON output. Got: %v", buf.String())
|
||||
}
|
||||
|
||||
// Verify the type URL is correct
|
||||
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
|
||||
if typeURL != expectedTypeURL {
|
||||
t.Errorf("Expected @type to be %s, got %s", expectedTypeURL, typeURL)
|
||||
}
|
||||
|
||||
t.Logf("Successfully wrote Any type with @type field: %s", buf.String())
|
||||
}
|
||||
|
||||
// TestCodecAnyTypeRead tests that JSON with @type field can be read into google.protobuf.Any
|
||||
func TestCodecAnyTypeRead(t *testing.T) {
|
||||
// JSON representation of an Any message with @type field
|
||||
jsonData := `{"@type":"type.googleapis.com/google.protobuf.StringValue","value":"test value"}`
|
||||
|
||||
buf := &mockReadWriteCloser{Buffer: bytes.NewBufferString(jsonData + "\n")}
|
||||
c := NewCodec(buf).(*Codec)
|
||||
|
||||
// Read into an Any message
|
||||
anyMsg := &anypb.Any{}
|
||||
if err := c.ReadBody(anyMsg); err != nil {
|
||||
t.Fatalf("Failed to read Any message: %v", err)
|
||||
}
|
||||
|
||||
// Verify the type URL is set
|
||||
expectedTypeURL := "type.googleapis.com/google.protobuf.StringValue"
|
||||
if anyMsg.TypeUrl != expectedTypeURL {
|
||||
t.Errorf("Expected TypeUrl to be %s, got %s", expectedTypeURL, anyMsg.TypeUrl)
|
||||
}
|
||||
|
||||
// Unmarshal the contained message
|
||||
stringValue := &wrapperspb.StringValue{}
|
||||
if err := anyMsg.UnmarshalTo(stringValue); err != nil {
|
||||
t.Fatalf("Failed to unmarshal contained message: %v", err)
|
||||
}
|
||||
|
||||
// Verify the value
|
||||
expectedValue := "test value"
|
||||
if stringValue.Value != expectedValue {
|
||||
t.Errorf("Expected value to be %s, got %s", expectedValue, stringValue.Value)
|
||||
}
|
||||
|
||||
t.Logf("Successfully read Any type from JSON with @type field")
|
||||
}
|
||||
+3
-17
@@ -5,9 +5,9 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"go-micro.dev/v5/codec"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type Codec struct {
|
||||
@@ -25,12 +25,7 @@ func (c *Codec) ReadBody(b interface{}) error {
|
||||
return nil
|
||||
}
|
||||
if pb, ok := b.(proto.Message); ok {
|
||||
// Read all JSON data from decoder
|
||||
var raw json.RawMessage
|
||||
if err := c.Decoder.Decode(&raw); err != nil {
|
||||
return err
|
||||
}
|
||||
return protojson.Unmarshal(raw, pb)
|
||||
return jsonpb.UnmarshalNext(c.Decoder, pb)
|
||||
}
|
||||
return c.Decoder.Decode(b)
|
||||
}
|
||||
@@ -39,15 +34,6 @@ func (c *Codec) Write(m *codec.Message, b interface{}) error {
|
||||
if b == nil {
|
||||
return nil
|
||||
}
|
||||
if pb, ok := b.(proto.Message); ok {
|
||||
data, err := protojson.Marshal(pb)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Write the marshaled data to the encoder
|
||||
var raw json.RawMessage = data
|
||||
return c.Encoder.Encode(raw)
|
||||
}
|
||||
return c.Encoder.Encode(b)
|
||||
}
|
||||
|
||||
|
||||
+15
-7
@@ -1,28 +1,36 @@
|
||||
package json
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/oxtoacart/bpool"
|
||||
)
|
||||
|
||||
var protojsonMarshaler = protojson.MarshalOptions{
|
||||
EmitUnpopulated: false,
|
||||
}
|
||||
var jsonpbMarshaler = &jsonpb.Marshaler{}
|
||||
|
||||
// create buffer pool with 16 instances each preallocated with 256 bytes.
|
||||
var bufferPool = bpool.NewSizedBufferPool(16, 256)
|
||||
|
||||
type Marshaler struct{}
|
||||
|
||||
func (j Marshaler) Marshal(v interface{}) ([]byte, error) {
|
||||
if pb, ok := v.(proto.Message); ok {
|
||||
return protojsonMarshaler.Marshal(pb)
|
||||
buf := bufferPool.Get()
|
||||
defer bufferPool.Put(buf)
|
||||
if err := jsonpbMarshaler.Marshal(buf, pb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
|
||||
func (j Marshaler) Unmarshal(d []byte, v interface{}) error {
|
||||
if pb, ok := v.(proto.Message); ok {
|
||||
return protojson.Unmarshal(d, pb)
|
||||
return jsonpb.Unmarshal(bytes.NewReader(d), pb)
|
||||
}
|
||||
return json.Unmarshal(d, v)
|
||||
}
|
||||
|
||||
@@ -1,189 +0,0 @@
|
||||
# TLS Security Migration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This document provides guidance for migrating to secure TLS certificate verification in go-micro v5.
|
||||
|
||||
## Current Status (v5)
|
||||
|
||||
**Default Behavior**: TLS certificate verification is **disabled** by default (`InsecureSkipVerify: true`)
|
||||
|
||||
**Reason**: Backward compatibility with existing deployments to avoid breaking production systems during routine upgrades.
|
||||
|
||||
**Security Risk**: The default behavior is vulnerable to man-in-the-middle (MITM) attacks.
|
||||
|
||||
## Migration Path
|
||||
|
||||
### Option 1: Enable Secure Mode (RECOMMENDED)
|
||||
|
||||
Set the environment variable to enable certificate verification:
|
||||
|
||||
```bash
|
||||
export MICRO_TLS_SECURE=true
|
||||
```
|
||||
|
||||
This enables proper TLS certificate verification while maintaining compatibility with v5.
|
||||
|
||||
### Option 2: Use SecureConfig Directly
|
||||
|
||||
In your code, explicitly use the secure configuration:
|
||||
|
||||
```go
|
||||
import (
|
||||
"go-micro.dev/v5/broker"
|
||||
mls "go-micro.dev/v5/util/tls"
|
||||
)
|
||||
|
||||
// Create broker with secure TLS config
|
||||
b := broker.NewHttpBroker(
|
||||
broker.TLSConfig(mls.SecureConfig()),
|
||||
)
|
||||
```
|
||||
|
||||
### Option 3: Provide Custom TLS Configuration
|
||||
|
||||
For fine-grained control, provide your own TLS configuration:
|
||||
|
||||
```go
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"go-micro.dev/v5/broker"
|
||||
"io/ioutil"
|
||||
)
|
||||
|
||||
// Load CA certificates
|
||||
caCert, err := ioutil.ReadFile("/path/to/ca-cert.pem")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
caCertPool := x509.NewCertPool()
|
||||
caCertPool.AppendCertsFromPEM(caCert)
|
||||
|
||||
// Create custom TLS config
|
||||
tlsConfig := &tls.Config{
|
||||
RootCAs: caCertPool,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
|
||||
// Create broker with custom config
|
||||
b := broker.NewHttpBroker(
|
||||
broker.TLSConfig(tlsConfig),
|
||||
)
|
||||
```
|
||||
|
||||
## Production Deployment Strategy
|
||||
|
||||
### Rolling Upgrade Considerations
|
||||
|
||||
The current implementation maintains backward compatibility, allowing safe rolling upgrades:
|
||||
|
||||
1. **Mixed Version Deployments**: v5 instances can communicate regardless of TLS security settings
|
||||
2. **No Immediate Breaking Changes**: Systems continue working with existing behavior
|
||||
3. **Gradual Migration**: Enable security incrementally across your infrastructure
|
||||
|
||||
### Recommended Approach
|
||||
|
||||
1. **Test in Staging**:
|
||||
```bash
|
||||
# In staging environment
|
||||
export MICRO_TLS_SECURE=true
|
||||
```
|
||||
|
||||
2. **Deploy with Feature Flag**: Use environment-based configuration for gradual rollout
|
||||
|
||||
3. **Monitor for Issues**: Watch for TLS handshake failures or certificate validation errors
|
||||
|
||||
4. **Full Production Rollout**: Once validated, enable across all services
|
||||
|
||||
### Multi-Host/Multi-Process Considerations
|
||||
|
||||
**Certificate Trust**: When enabling secure mode, ensure:
|
||||
|
||||
1. All hosts trust the same root CAs
|
||||
2. Self-signed certificates are properly distributed if used
|
||||
3. Certificate validity periods are monitored
|
||||
4. Certificate chains are complete
|
||||
|
||||
**Service Mesh Alternative**: Consider using a service mesh (Istio, Linkerd, etc.) for:
|
||||
- Automatic mTLS between services
|
||||
- Certificate management and rotation
|
||||
- No application code changes required
|
||||
|
||||
## Future Changes (v6)
|
||||
|
||||
In go-micro v6, the default will change to **secure by default**:
|
||||
|
||||
- `InsecureSkipVerify: false` (certificate verification enabled)
|
||||
- Breaking change requiring major version bump
|
||||
- Migration completed before v6 release avoids disruption
|
||||
|
||||
## Testing Your Migration
|
||||
|
||||
### Verify Secure Mode is Active
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
mls "go-micro.dev/v5/util/tls"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
os.Setenv("MICRO_TLS_SECURE", "true")
|
||||
config := mls.Config()
|
||||
fmt.Printf("InsecureSkipVerify: %v (should be false)\n", config.InsecureSkipVerify)
|
||||
}
|
||||
```
|
||||
|
||||
### Test Certificate Validation
|
||||
|
||||
Create a test service and verify it:
|
||||
- Accepts valid certificates
|
||||
- Rejects invalid/self-signed certificates (when not in CA)
|
||||
- Properly validates certificate chains
|
||||
|
||||
## Common Issues and Solutions
|
||||
|
||||
### Issue: "x509: certificate signed by unknown authority"
|
||||
|
||||
**Cause**: The server certificate is not signed by a trusted CA
|
||||
|
||||
**Solution**:
|
||||
1. Add the CA certificate to the trusted root CAs
|
||||
2. Use a properly signed certificate
|
||||
3. For development only: Use `InsecureConfig()` explicitly
|
||||
|
||||
### Issue: "x509: certificate has expired"
|
||||
|
||||
**Cause**: Server certificate has expired
|
||||
|
||||
**Solution**:
|
||||
1. Renew the certificate
|
||||
2. Implement certificate rotation
|
||||
3. Monitor certificate expiry dates
|
||||
|
||||
### Issue: Services can't communicate after enabling secure mode
|
||||
|
||||
**Cause**: Mixed certificate authorities or missing certificates
|
||||
|
||||
**Solution**:
|
||||
1. Ensure all services use certificates from the same CA
|
||||
2. Distribute CA certificates to all nodes
|
||||
3. Verify certificate SANs match service addresses
|
||||
|
||||
## Questions?
|
||||
|
||||
For issues or questions about TLS security migration, please:
|
||||
- Open an issue on GitHub
|
||||
- Check the documentation at https://go-micro.dev/docs/
|
||||
- Review the security guidelines
|
||||
|
||||
## Security Resources
|
||||
|
||||
- [OWASP TLS Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html)
|
||||
- [Go TLS Documentation](https://pkg.go.dev/crypto/tls)
|
||||
- [Certificate Best Practices](https://www.ssl.com/guide/ssl-best-practices/)
|
||||
@@ -1,67 +0,0 @@
|
||||
# TLS Security Update - Important Information
|
||||
|
||||
## What Changed
|
||||
|
||||
The TLS configuration in go-micro now includes a security deprecation warning.
|
||||
|
||||
## Current Behavior (v5.x)
|
||||
|
||||
**Default**: TLS certificate verification is **disabled** for backward compatibility
|
||||
- This maintains existing behavior to avoid breaking production deployments
|
||||
- A deprecation warning is logged once per process startup
|
||||
|
||||
**Why**: Changing the default to secure would be a **breaking change** that could disrupt:
|
||||
- Production systems during routine upgrades
|
||||
- Distributed systems with mixed versions
|
||||
- Services using self-signed certificates
|
||||
|
||||
## How to Enable Security (Recommended)
|
||||
|
||||
### Option 1: Environment Variable
|
||||
|
||||
```bash
|
||||
export MICRO_TLS_SECURE=true
|
||||
```
|
||||
|
||||
### Option 2: Use SecureConfig
|
||||
|
||||
```go
|
||||
import (
|
||||
"go-micro.dev/v5/broker"
|
||||
mls "go-micro.dev/v5/util/tls"
|
||||
)
|
||||
|
||||
broker := broker.NewHttpBroker(
|
||||
broker.TLSConfig(mls.SecureConfig()),
|
||||
)
|
||||
```
|
||||
|
||||
## Migration Timeline
|
||||
|
||||
- **v5.x (Current)**: Insecure by default, opt-in security via `MICRO_TLS_SECURE=true`
|
||||
- **v6.x (Future)**: Secure by default (breaking change with major version bump)
|
||||
|
||||
## Why This Approach?
|
||||
|
||||
This addresses the concerns raised about:
|
||||
|
||||
1. **Major version requirements**: No breaking change in v5, deferred to v6
|
||||
2. **Cross-host compatibility**: All hosts use same default behavior
|
||||
3. **Production safety**: Existing deployments continue working during upgrades
|
||||
4. **Migration path**: Clear opt-in path with documentation
|
||||
|
||||
## Documentation
|
||||
|
||||
See [SECURITY_MIGRATION.md](./SECURITY_MIGRATION.md) for detailed migration guide.
|
||||
|
||||
## Security Recommendation
|
||||
|
||||
For production deployments:
|
||||
1. Test with `MICRO_TLS_SECURE=true` in staging
|
||||
2. Use proper CA-signed certificates
|
||||
3. Consider service mesh (Istio, Linkerd) for automatic mTLS
|
||||
4. Plan migration before v6 release
|
||||
|
||||
## Questions?
|
||||
|
||||
Open an issue on GitHub or check the documentation at https://go-micro.dev/docs/
|
||||
@@ -1,162 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
dlog "go-micro.dev/v5/debug/log"
|
||||
)
|
||||
|
||||
// debugLogHandler is a slog handler that writes to the debug/log buffer
|
||||
type debugLogHandler struct {
|
||||
level slog.Leveler
|
||||
attrs []slog.Attr
|
||||
group string
|
||||
}
|
||||
|
||||
// newDebugLogHandler creates a new handler that writes to debug/log
|
||||
func newDebugLogHandler(level slog.Leveler) *debugLogHandler {
|
||||
return &debugLogHandler{
|
||||
level: level,
|
||||
attrs: make([]slog.Attr, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) Enabled(_ context.Context, level slog.Level) bool {
|
||||
return level >= h.level.Level()
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) Handle(_ context.Context, r slog.Record) error {
|
||||
// Build metadata from attributes
|
||||
metadata := make(map[string]string)
|
||||
|
||||
// Add handler's attributes
|
||||
for _, attr := range h.attrs {
|
||||
metadata[attr.Key] = attr.Value.String()
|
||||
}
|
||||
|
||||
// Add record's attributes
|
||||
r.Attrs(func(a slog.Attr) bool {
|
||||
metadata[a.Key] = a.Value.String()
|
||||
return true
|
||||
})
|
||||
|
||||
// Add level to metadata
|
||||
metadata["level"] = r.Level.String()
|
||||
|
||||
// Add source if available
|
||||
if sourcePath := extractSourceFilePath(r.PC); sourcePath != "" {
|
||||
metadata["file"] = sourcePath
|
||||
}
|
||||
|
||||
// Create debug log record
|
||||
rec := dlog.Record{
|
||||
Timestamp: r.Time,
|
||||
Message: r.Message,
|
||||
Metadata: metadata,
|
||||
}
|
||||
|
||||
// Write to debug log
|
||||
_ = dlog.DefaultLog.Write(rec)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
newAttrs := make([]slog.Attr, len(h.attrs)+len(attrs))
|
||||
copy(newAttrs, h.attrs)
|
||||
copy(newAttrs[len(h.attrs):], attrs)
|
||||
|
||||
return &debugLogHandler{
|
||||
level: h.level,
|
||||
attrs: newAttrs,
|
||||
group: h.group,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *debugLogHandler) WithGroup(name string) slog.Handler {
|
||||
// For simplicity, we'll just track the group name
|
||||
// A full implementation would nest attributes properly
|
||||
return &debugLogHandler{
|
||||
level: h.level,
|
||||
attrs: h.attrs,
|
||||
group: name,
|
||||
}
|
||||
}
|
||||
|
||||
// multiHandler sends records to multiple handlers
|
||||
type multiHandler struct {
|
||||
handlers []slog.Handler
|
||||
}
|
||||
|
||||
func newMultiHandler(handlers ...slog.Handler) *multiHandler {
|
||||
return &multiHandler{
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *multiHandler) Enabled(ctx context.Context, level slog.Level) bool {
|
||||
// Enabled if any handler is enabled
|
||||
for _, handler := range h.handlers {
|
||||
if handler.Enabled(ctx, level) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (h *multiHandler) Handle(ctx context.Context, r slog.Record) error {
|
||||
for _, handler := range h.handlers {
|
||||
// Clone the record for each handler to avoid issues
|
||||
if err := handler.Handle(ctx, r.Clone()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *multiHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
|
||||
newHandlers := make([]slog.Handler, len(h.handlers))
|
||||
for i, handler := range h.handlers {
|
||||
newHandlers[i] = handler.WithAttrs(attrs)
|
||||
}
|
||||
return &multiHandler{handlers: newHandlers}
|
||||
}
|
||||
|
||||
func (h *multiHandler) WithGroup(name string) slog.Handler {
|
||||
newHandlers := make([]slog.Handler, len(h.handlers))
|
||||
for i, handler := range h.handlers {
|
||||
newHandlers[i] = handler.WithGroup(name)
|
||||
}
|
||||
return &multiHandler{handlers: newHandlers}
|
||||
}
|
||||
|
||||
// extractSourceFilePath extracts the package/file:line from a PC
|
||||
func extractSourceFilePath(pc uintptr) string {
|
||||
if pc == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
fs := runtime.CallersFrames([]uintptr{pc})
|
||||
f, _ := fs.Next()
|
||||
if f.File == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Extract just filename, not full path
|
||||
idx := strings.LastIndexByte(f.File, '/')
|
||||
if idx == -1 {
|
||||
return fmt.Sprintf("%s:%d", f.File, f.Line)
|
||||
}
|
||||
|
||||
// Get package/file:line
|
||||
idx2 := strings.LastIndexByte(f.File[:idx], '/')
|
||||
if idx2 == -1 {
|
||||
return fmt.Sprintf("%s:%d", f.File[idx+1:], f.Line)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", f.File[idx2+1:], f.Line)
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
dlog "go-micro.dev/v5/debug/log"
|
||||
)
|
||||
|
||||
func TestDebugLogBuffer(t *testing.T) {
|
||||
// Create a new logger
|
||||
l := NewLogger(WithLevel(InfoLevel))
|
||||
|
||||
// Log some messages
|
||||
l.Log(InfoLevel, "test message 1")
|
||||
l.Log(WarnLevel, "test message 2")
|
||||
l.Logf(ErrorLevel, "formatted message %d", 3)
|
||||
|
||||
// Read from debug log buffer
|
||||
records, err := dlog.DefaultLog.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read from debug log: %v", err)
|
||||
}
|
||||
|
||||
// We should have at least our 3 messages
|
||||
if len(records) < 3 {
|
||||
t.Fatalf("Expected at least 3 log records in debug buffer, got %d", len(records))
|
||||
}
|
||||
|
||||
// Check that our messages are there
|
||||
foundCount := 0
|
||||
for _, rec := range records {
|
||||
msg, ok := rec.Message.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if msg == "test message 1" || msg == "test message 2" || msg == "formatted message 3" {
|
||||
foundCount++
|
||||
// Verify metadata is present
|
||||
if rec.Metadata == nil {
|
||||
t.Errorf("Record has nil metadata")
|
||||
}
|
||||
// Verify level is in metadata
|
||||
if _, ok := rec.Metadata["level"]; !ok {
|
||||
t.Errorf("Record missing level in metadata")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if foundCount < 3 {
|
||||
t.Errorf("Expected to find 3 specific messages in debug log, found %d", foundCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDebugLogWithFields(t *testing.T) {
|
||||
// Create a logger with fields
|
||||
l := NewLogger(WithLevel(InfoLevel), WithFields(map[string]interface{}{
|
||||
"service": "test",
|
||||
"version": "1.0",
|
||||
}))
|
||||
|
||||
// Log a message
|
||||
l.Log(InfoLevel, "message with fields")
|
||||
|
||||
// Read from debug log buffer
|
||||
records, err := dlog.DefaultLog.Read()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to read from debug log: %v", err)
|
||||
}
|
||||
|
||||
// Find our message
|
||||
found := false
|
||||
for _, rec := range records {
|
||||
msg, ok := rec.Message.(string)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if msg == "message with fields" {
|
||||
found = true
|
||||
// Verify fields are in metadata
|
||||
if rec.Metadata["service"] != "test" {
|
||||
t.Errorf("Expected service=test in metadata, got %s", rec.Metadata["service"])
|
||||
}
|
||||
if rec.Metadata["version"] != "1.0" {
|
||||
t.Errorf("Expected version=1.0 in metadata, got %s", rec.Metadata["version"])
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Error("Did not find message with fields in debug log")
|
||||
}
|
||||
}
|
||||
+104
-71
@@ -3,11 +3,14 @@ package logger
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
dlog "go-micro.dev/v5/debug/log"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -21,47 +24,15 @@ func init() {
|
||||
|
||||
type defaultLogger struct {
|
||||
opts Options
|
||||
slog *slog.Logger
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
// Init (opts...) should only overwrite provided options.
|
||||
func (l *defaultLogger) Init(opts ...Option) error {
|
||||
l.Lock()
|
||||
defer l.Unlock()
|
||||
|
||||
for _, o := range opts {
|
||||
o(&l.opts)
|
||||
}
|
||||
|
||||
// Recreate slog logger with new options
|
||||
handlerOpts := &slog.HandlerOptions{
|
||||
Level: l.opts.Level.ToSlog(),
|
||||
AddSource: true,
|
||||
}
|
||||
|
||||
// Create text handler for stdout
|
||||
textHandler := slog.NewTextHandler(l.opts.Out, handlerOpts)
|
||||
|
||||
// Create debug log handler for debug/log buffer
|
||||
debugHandler := newDebugLogHandler(handlerOpts.Level)
|
||||
|
||||
// Combine both handlers
|
||||
handler := newMultiHandler(textHandler, debugHandler)
|
||||
|
||||
l.slog = slog.New(handler)
|
||||
|
||||
// Add fields if any
|
||||
if len(l.opts.Fields) > 0 {
|
||||
const fieldsPerKV = 2
|
||||
args := make([]any, 0, len(l.opts.Fields)*fieldsPerKV)
|
||||
for k, v := range l.opts.Fields {
|
||||
args = append(args, k, v)
|
||||
}
|
||||
|
||||
l.slog = l.slog.With(args...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -70,24 +41,25 @@ func (l *defaultLogger) String() string {
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Fields(fields map[string]interface{}) Logger {
|
||||
l.RLock()
|
||||
nfields := copyFields(l.opts.Fields)
|
||||
opts := l.opts
|
||||
l.RUnlock()
|
||||
l.Lock()
|
||||
nfields := make(map[string]interface{}, len(l.opts.Fields))
|
||||
|
||||
for k, v := range l.opts.Fields {
|
||||
nfields[k] = v
|
||||
}
|
||||
l.Unlock()
|
||||
|
||||
for k, v := range fields {
|
||||
nfields[k] = v
|
||||
}
|
||||
|
||||
// Create new logger without locks
|
||||
newLogger := NewLogger(
|
||||
WithLevel(opts.Level),
|
||||
WithFields(nfields),
|
||||
WithOutput(opts.Out),
|
||||
WithCallerSkipCount(opts.CallerSkipCount),
|
||||
)
|
||||
|
||||
return newLogger
|
||||
return &defaultLogger{opts: Options{
|
||||
Level: l.opts.Level,
|
||||
Fields: nfields,
|
||||
Out: l.opts.Out,
|
||||
CallerSkipCount: l.opts.CallerSkipCount,
|
||||
Context: l.opts.Context,
|
||||
}}
|
||||
}
|
||||
|
||||
func copyFields(src map[string]interface{}) map[string]interface{} {
|
||||
@@ -99,6 +71,33 @@ func copyFields(src map[string]interface{}) map[string]interface{} {
|
||||
return dst
|
||||
}
|
||||
|
||||
// logCallerfilePath returns a package/file:line description of the caller,
|
||||
// preserving only the leaf directory name and file name.
|
||||
func logCallerfilePath(loggingFilePath string) string {
|
||||
// To make sure we trim the path correctly on Windows too, we
|
||||
// counter-intuitively need to use '/' and *not* os.PathSeparator here,
|
||||
// because the path given originates from Go stdlib, specifically
|
||||
// runtime.Caller() which (as of Mar/17) returns forward slashes even on
|
||||
// Windows.
|
||||
//
|
||||
// See https://github.com/golang/go/issues/3335
|
||||
// and https://github.com/golang/go/issues/18151
|
||||
//
|
||||
// for discussion on the issue on Go side.
|
||||
idx := strings.LastIndexByte(loggingFilePath, '/')
|
||||
if idx == -1 {
|
||||
return loggingFilePath
|
||||
}
|
||||
|
||||
idx = strings.LastIndexByte(loggingFilePath[:idx], '/')
|
||||
|
||||
if idx == -1 {
|
||||
return loggingFilePath
|
||||
}
|
||||
|
||||
return loggingFilePath[idx+1:]
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Log(level Level, v ...interface{}) {
|
||||
// TODO decide does we need to write message if log level not used?
|
||||
if !l.opts.Level.Enabled(level) {
|
||||
@@ -106,21 +105,39 @@ func (l *defaultLogger) Log(level Level, v ...interface{}) {
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
slogger := l.slog
|
||||
|
||||
if slogger == nil {
|
||||
// Fallback if not initialized
|
||||
slogger = slog.Default()
|
||||
}
|
||||
fields := copyFields(l.opts.Fields)
|
||||
l.RUnlock()
|
||||
|
||||
// Get caller information
|
||||
var pcs [1]uintptr
|
||||
fields["level"] = level.String()
|
||||
|
||||
runtime.Callers(l.opts.CallerSkipCount, pcs[:])
|
||||
r := slog.NewRecord(time.Now(), level.ToSlog(), fmt.Sprint(v...), pcs[0])
|
||||
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
|
||||
fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
|
||||
}
|
||||
|
||||
_ = slogger.Handler().Handle(context.Background(), r)
|
||||
rec := dlog.Record{
|
||||
Timestamp: time.Now(),
|
||||
Message: fmt.Sprint(v...),
|
||||
Metadata: make(map[string]string, len(fields)),
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(fields))
|
||||
for k, v := range fields {
|
||||
keys = append(keys, k)
|
||||
rec.Metadata[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
metadata := ""
|
||||
|
||||
for _, k := range keys {
|
||||
metadata += fmt.Sprintf(" %s=%v", k, fields[k])
|
||||
}
|
||||
|
||||
dlog.DefaultLog.Write(rec)
|
||||
|
||||
t := rec.Timestamp.Format("2006-01-02 15:04:05")
|
||||
fmt.Printf("%s %s %v\n", t, metadata, rec.Message)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
|
||||
@@ -130,21 +147,39 @@ func (l *defaultLogger) Logf(level Level, format string, v ...interface{}) {
|
||||
}
|
||||
|
||||
l.RLock()
|
||||
slogger := l.slog
|
||||
|
||||
if slogger == nil {
|
||||
// Fallback if not initialized
|
||||
slogger = slog.Default()
|
||||
}
|
||||
fields := copyFields(l.opts.Fields)
|
||||
l.RUnlock()
|
||||
|
||||
// Get caller information
|
||||
var pcs [1]uintptr
|
||||
fields["level"] = level.String()
|
||||
|
||||
runtime.Callers(l.opts.CallerSkipCount, pcs[:])
|
||||
r := slog.NewRecord(time.Now(), level.ToSlog(), fmt.Sprintf(format, v...), pcs[0])
|
||||
if _, file, line, ok := runtime.Caller(l.opts.CallerSkipCount); ok {
|
||||
fields["file"] = fmt.Sprintf("%s:%d", logCallerfilePath(file), line)
|
||||
}
|
||||
|
||||
_ = slogger.Handler().Handle(context.Background(), r)
|
||||
rec := dlog.Record{
|
||||
Timestamp: time.Now(),
|
||||
Message: fmt.Sprintf(format, v...),
|
||||
Metadata: make(map[string]string, len(fields)),
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(fields))
|
||||
for k, v := range fields {
|
||||
keys = append(keys, k)
|
||||
rec.Metadata[k] = fmt.Sprintf("%v", v)
|
||||
}
|
||||
|
||||
sort.Strings(keys)
|
||||
|
||||
metadata := ""
|
||||
|
||||
for _, k := range keys {
|
||||
metadata += fmt.Sprintf(" %s=%v", k, fields[k])
|
||||
}
|
||||
|
||||
dlog.DefaultLog.Write(rec)
|
||||
|
||||
t := rec.Timestamp.Format("2006-01-02 15:04:05")
|
||||
fmt.Printf("%s %s %v\n", t, metadata, rec.Message)
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Options() Options {
|
||||
@@ -161,13 +196,11 @@ func (l *defaultLogger) Options() Options {
|
||||
// NewLogger builds a new logger based on options.
|
||||
func NewLogger(opts ...Option) Logger {
|
||||
// Default options
|
||||
const defaultCallerSkipCount = 2
|
||||
|
||||
options := Options{
|
||||
Level: InfoLevel,
|
||||
Fields: make(map[string]interface{}),
|
||||
Out: os.Stderr,
|
||||
CallerSkipCount: defaultCallerSkipCount,
|
||||
CallerSkipCount: 2,
|
||||
Context: context.Background(),
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -47,31 +46,6 @@ func (l Level) Enabled(lvl Level) bool {
|
||||
return lvl >= l
|
||||
}
|
||||
|
||||
// ToSlog converts our Level to slog.Level.
|
||||
func (l Level) ToSlog() slog.Level {
|
||||
const (
|
||||
traceLevelOffset = 4
|
||||
fatalLevelOffset = 4
|
||||
)
|
||||
|
||||
switch l {
|
||||
case TraceLevel:
|
||||
return slog.LevelDebug - traceLevelOffset // Lower than Debug
|
||||
case DebugLevel:
|
||||
return slog.LevelDebug
|
||||
case InfoLevel:
|
||||
return slog.LevelInfo
|
||||
case WarnLevel:
|
||||
return slog.LevelWarn
|
||||
case ErrorLevel:
|
||||
return slog.LevelError
|
||||
case FatalLevel:
|
||||
return slog.LevelError + fatalLevelOffset // Higher than Error
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
// GetLevel converts a level string into a logger Level value.
|
||||
// returns an error if the input string does not match known values.
|
||||
func GetLevel(levelStr string) (Level, error) {
|
||||
|
||||
+11
-7
@@ -4,13 +4,15 @@ import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
b "bytes"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"go-micro.dev/v5/codec"
|
||||
"go-micro.dev/v5/codec/bytes"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/encoding"
|
||||
"google.golang.org/grpc/metadata"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type jsonCodec struct{}
|
||||
@@ -18,9 +20,10 @@ type bytesCodec struct{}
|
||||
type protoCodec struct{}
|
||||
type wrapCodec struct{ encoding.Codec }
|
||||
|
||||
var protojsonMarshaler = protojson.MarshalOptions{
|
||||
UseProtoNames: true,
|
||||
EmitUnpopulated: false,
|
||||
var jsonpbMarshaler = &jsonpb.Marshaler{
|
||||
EnumsAsInts: false,
|
||||
EmitDefaults: false,
|
||||
OrigName: true,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -82,7 +85,8 @@ func (protoCodec) Name() string {
|
||||
|
||||
func (jsonCodec) Marshal(v interface{}) ([]byte, error) {
|
||||
if pb, ok := v.(proto.Message); ok {
|
||||
return protojsonMarshaler.Marshal(pb)
|
||||
s, err := jsonpbMarshaler.MarshalToString(pb)
|
||||
return []byte(s), err
|
||||
}
|
||||
|
||||
return json.Marshal(v)
|
||||
@@ -93,7 +97,7 @@ func (jsonCodec) Unmarshal(data []byte, v interface{}) error {
|
||||
return nil
|
||||
}
|
||||
if pb, ok := v.(proto.Message); ok {
|
||||
return protojson.Unmarshal(data, pb)
|
||||
return jsonpb.Unmarshal(b.NewReader(data), pb)
|
||||
}
|
||||
return json.Unmarshal(data, v)
|
||||
}
|
||||
|
||||
+6
-35
@@ -10,56 +10,27 @@ import (
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"log"
|
||||
"math/big"
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// Track if we've already logged the warning to avoid spam
|
||||
warningOnce sync.Once
|
||||
)
|
||||
|
||||
// Config returns a TLS config.
|
||||
//
|
||||
// BACKWARD COMPATIBILITY: By default, InsecureSkipVerify is true for compatibility
|
||||
// with existing deployments. This maintains the existing behavior to avoid breaking
|
||||
// production systems during upgrades.
|
||||
//
|
||||
// SECURITY WARNING: The default behavior skips certificate verification. This is
|
||||
// insecure and vulnerable to man-in-the-middle attacks.
|
||||
//
|
||||
// To enable secure certificate verification (RECOMMENDED for production):
|
||||
// - Set environment variable: MICRO_TLS_SECURE=true
|
||||
// - Use SecureConfig() function directly
|
||||
// - Configure TLSConfig with proper certificates
|
||||
// By default, InsecureSkipVerify is true for local development.
|
||||
// For production, either:
|
||||
// - Set MICRO_TLS_SECURE=true with proper CA certs
|
||||
// - Use a service mesh (Istio, Linkerd) for mTLS
|
||||
//
|
||||
// DEPRECATION NOTICE: The insecure default will be changed in a future major version (v6).
|
||||
// Please migrate to secure mode by setting MICRO_TLS_SECURE=true in your environment.
|
||||
// - Configure TLSConfig directly with your certs
|
||||
func Config() *tls.Config {
|
||||
// Check environment for explicit secure mode
|
||||
// Check environment for secure mode
|
||||
if os.Getenv("MICRO_TLS_SECURE") == "true" {
|
||||
return &tls.Config{
|
||||
InsecureSkipVerify: false,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
}
|
||||
}
|
||||
|
||||
// Log deprecation warning once (only if not in test environment)
|
||||
if os.Getenv("IN_TRAVIS_CI") == "" {
|
||||
warningOnce.Do(func() {
|
||||
log.Println("[SECURITY WARNING] TLS certificate verification is disabled by default. " +
|
||||
"This is insecure and will change in v6. " +
|
||||
"Set MICRO_TLS_SECURE=true to enable certificate verification.")
|
||||
})
|
||||
}
|
||||
|
||||
// DEPRECATED: Default remains insecure for backward compatibility
|
||||
// This will change in v6 - please migrate to secure mode
|
||||
// Default: insecure for local development
|
||||
return &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
MinVersion: tls.VersionTLS12,
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
envVar string
|
||||
envValue string
|
||||
wantInsecure bool
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "default_insecure_for_backward_compatibility",
|
||||
envVar: "",
|
||||
envValue: "",
|
||||
wantInsecure: true,
|
||||
description: "Default should remain insecure for backward compatibility (will change in v6)",
|
||||
},
|
||||
{
|
||||
name: "secure_mode_enabled",
|
||||
envVar: "MICRO_TLS_SECURE",
|
||||
envValue: "true",
|
||||
wantInsecure: false,
|
||||
description: "MICRO_TLS_SECURE=true should enable certificate verification",
|
||||
},
|
||||
{
|
||||
name: "secure_mode_disabled",
|
||||
envVar: "MICRO_TLS_SECURE",
|
||||
envValue: "false",
|
||||
wantInsecure: true,
|
||||
description: "MICRO_TLS_SECURE=false should remain insecure",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Clean up environment
|
||||
os.Unsetenv("MICRO_TLS_SECURE")
|
||||
os.Unsetenv("MICRO_TLS_INSECURE")
|
||||
// Suppress warning in tests
|
||||
os.Setenv("IN_TRAVIS_CI", "yes")
|
||||
defer os.Unsetenv("IN_TRAVIS_CI")
|
||||
|
||||
// Set environment variable if specified
|
||||
if tt.envVar != "" {
|
||||
os.Setenv(tt.envVar, tt.envValue)
|
||||
defer os.Unsetenv(tt.envVar)
|
||||
}
|
||||
|
||||
config := Config()
|
||||
|
||||
if config == nil {
|
||||
t.Fatal("Config() returned nil")
|
||||
}
|
||||
|
||||
if config.InsecureSkipVerify != tt.wantInsecure {
|
||||
t.Errorf("%s: InsecureSkipVerify = %v, want %v",
|
||||
tt.description, config.InsecureSkipVerify, tt.wantInsecure)
|
||||
}
|
||||
|
||||
// Verify MinVersion is set correctly
|
||||
if config.MinVersion == 0 {
|
||||
t.Error("MinVersion should be set")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecureConfig(t *testing.T) {
|
||||
config := SecureConfig()
|
||||
|
||||
if config == nil {
|
||||
t.Fatal("SecureConfig() returned nil")
|
||||
}
|
||||
|
||||
if config.InsecureSkipVerify {
|
||||
t.Error("SecureConfig should have InsecureSkipVerify set to false")
|
||||
}
|
||||
|
||||
if config.MinVersion == 0 {
|
||||
t.Error("MinVersion should be set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsecureConfig(t *testing.T) {
|
||||
config := InsecureConfig()
|
||||
|
||||
if config == nil {
|
||||
t.Fatal("InsecureConfig() returned nil")
|
||||
}
|
||||
|
||||
if !config.InsecureSkipVerify {
|
||||
t.Error("InsecureConfig should have InsecureSkipVerify set to true")
|
||||
}
|
||||
|
||||
if config.MinVersion == 0 {
|
||||
t.Error("MinVersion should be set")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user