 131868bdca
			
		
	
	131868bdca
	
	
	
		
			
			Major security, observability, and configuration improvements:
## Security Hardening
- Implemented configurable CORS (no more wildcards)
- Added comprehensive auth middleware for admin endpoints
- Enhanced webhook HMAC validation
- Added input validation and rate limiting
- Security headers and CSP policies
## Configuration Management
- Made N8N webhook URL configurable (WHOOSH_N8N_BASE_URL)
- Replaced all hardcoded endpoints with environment variables
- Added feature flags for LLM vs heuristic composition
- Gitea fetch hardening with EAGER_FILTER and FULL_RESCAN options
## API Completeness
- Implemented GetCouncilComposition function
- Added GET /api/v1/councils/{id} endpoint
- Council artifacts API (POST/GET /api/v1/councils/{id}/artifacts)
- /admin/health/details endpoint with component status
- Database lookup for repository URLs (no hardcoded fallbacks)
## Observability & Performance
- Added OpenTelemetry distributed tracing with goal/pulse correlation
- Performance optimization database indexes
- Comprehensive health monitoring
- Enhanced logging and error handling
## Infrastructure
- Production-ready P2P discovery (replaces mock implementation)
- Removed unused Redis configuration
- Enhanced Docker Swarm integration
- Added migration files for performance indexes
## Code Quality
- Comprehensive input validation
- Graceful error handling and failsafe fallbacks
- Backwards compatibility maintained
- Following security best practices
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
		
	
		
			
				
	
	
		
			186 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			186 lines
		
	
	
		
			4.9 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2022-2023 The NATS Authors
 | |
| // Licensed under the Apache License, Version 2.0 (the "License");
 | |
| // you may not use this file except in compliance with the License.
 | |
| // You may obtain a copy of the License at
 | |
| //
 | |
| // http://www.apache.org/licenses/LICENSE-2.0
 | |
| //
 | |
| // Unless required by applicable law or agreed to in writing, software
 | |
| // distributed under the License is distributed on an "AS IS" BASIS,
 | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| // See the License for the specific language governing permissions and
 | |
| // limitations under the License.
 | |
| 
 | |
| package nkeys
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"crypto/rand"
 | |
| 	"encoding/binary"
 | |
| 	"io"
 | |
| 
 | |
| 	"golang.org/x/crypto/curve25519"
 | |
| 	"golang.org/x/crypto/nacl/box"
 | |
| )
 | |
| 
 | |
| // This package will support safe use of X25519 keys for asymmetric encryption.
 | |
| // We will be compatible with nacl.Box, but generate random nonces automatically.
 | |
| // We may add more advanced options in the future for group recipients and better
 | |
| // end to end algorithms.
 | |
| 
 | |
| const (
 | |
| 	curveKeyLen    = 32
 | |
| 	curveDecodeLen = 35
 | |
| 	curveNonceLen  = 24
 | |
| )
 | |
| 
 | |
| type ckp struct {
 | |
| 	seed [curveKeyLen]byte // Private raw key.
 | |
| }
 | |
| 
 | |
| // CreateCurveKeys will create a Curve typed KeyPair.
 | |
| func CreateCurveKeys() (KeyPair, error) {
 | |
| 	return CreateCurveKeysWithRand(rand.Reader)
 | |
| }
 | |
| 
 | |
| // CreateCurveKeysWithRand will create a Curve typed KeyPair
 | |
| // with specified rand source.
 | |
| func CreateCurveKeysWithRand(rr io.Reader) (KeyPair, error) {
 | |
| 	var kp ckp
 | |
| 	_, err := io.ReadFull(rr, kp.seed[:])
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	return &kp, nil
 | |
| }
 | |
| 
 | |
| // Will create a curve key pair from seed.
 | |
| func FromCurveSeed(seed []byte) (KeyPair, error) {
 | |
| 	pb, raw, err := DecodeSeed(seed)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	if pb != PrefixByteCurve || len(raw) != curveKeyLen {
 | |
| 		return nil, ErrInvalidCurveSeed
 | |
| 	}
 | |
| 	var kp ckp
 | |
| 	copy(kp.seed[:], raw)
 | |
| 	return &kp, nil
 | |
| }
 | |
| 
 | |
| // Seed will return the encoded seed.
 | |
| func (pair *ckp) Seed() ([]byte, error) {
 | |
| 	return EncodeSeed(PrefixByteCurve, pair.seed[:])
 | |
| }
 | |
| 
 | |
| // PublicKey will return the encoded public key.
 | |
| func (pair *ckp) PublicKey() (string, error) {
 | |
| 	var pub [curveKeyLen]byte
 | |
| 	curve25519.ScalarBaseMult(&pub, &pair.seed)
 | |
| 	key, err := Encode(PrefixByteCurve, pub[:])
 | |
| 	return string(key), err
 | |
| }
 | |
| 
 | |
| // PrivateKey will return the encoded private key.
 | |
| func (pair *ckp) PrivateKey() ([]byte, error) {
 | |
| 	return Encode(PrefixBytePrivate, pair.seed[:])
 | |
| }
 | |
| 
 | |
| func decodePubCurveKey(src string, dest []byte) error {
 | |
| 	var raw [curveDecodeLen]byte // should always be 35
 | |
| 	n, err := b32Enc.Decode(raw[:], []byte(src))
 | |
| 	if err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 	if n != curveDecodeLen {
 | |
| 		return ErrInvalidCurveKey
 | |
| 	}
 | |
| 	// Make sure it is what we expected.
 | |
| 	if prefix := PrefixByte(raw[0]); prefix != PrefixByteCurve {
 | |
| 		return ErrInvalidPublicKey
 | |
| 	}
 | |
| 	var crc uint16
 | |
| 	end := n - 2
 | |
| 	sum := raw[end:n]
 | |
| 	checksum := bytes.NewReader(sum)
 | |
| 	if err := binary.Read(checksum, binary.LittleEndian, &crc); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// ensure checksum is valid
 | |
| 	if err := validate(raw[:end], crc); err != nil {
 | |
| 		return err
 | |
| 	}
 | |
| 
 | |
| 	// Copy over, ignore prefix byte.
 | |
| 	copy(dest, raw[1:end])
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Only version for now, but could add in X3DH in the future, etc.
 | |
| const XKeyVersionV1 = "xkv1"
 | |
| const vlen = len(XKeyVersionV1)
 | |
| 
 | |
| // Seal is compatible with nacl.Box.Seal() and can be used in similar situations for small messages.
 | |
| // We generate the nonce from crypto rand by default.
 | |
| func (pair *ckp) Seal(input []byte, recipient string) ([]byte, error) {
 | |
| 	return pair.SealWithRand(input, recipient, rand.Reader)
 | |
| }
 | |
| 
 | |
| func (pair *ckp) SealWithRand(input []byte, recipient string, rr io.Reader) ([]byte, error) {
 | |
| 	var (
 | |
| 		rpub  [curveKeyLen]byte
 | |
| 		nonce [curveNonceLen]byte
 | |
| 		out   [vlen + curveNonceLen]byte
 | |
| 		err   error
 | |
| 	)
 | |
| 
 | |
| 	if err = decodePubCurveKey(recipient, rpub[:]); err != nil {
 | |
| 		return nil, ErrInvalidRecipient
 | |
| 	}
 | |
| 	if _, err := io.ReadFull(rr, nonce[:]); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	copy(out[:vlen], []byte(XKeyVersionV1))
 | |
| 	copy(out[vlen:], nonce[:])
 | |
| 	return box.Seal(out[:], input, &nonce, &rpub, &pair.seed), nil
 | |
| }
 | |
| 
 | |
| func (pair *ckp) Open(input []byte, sender string) ([]byte, error) {
 | |
| 	if len(input) <= vlen+curveNonceLen {
 | |
| 		return nil, ErrInvalidEncrypted
 | |
| 	}
 | |
| 	var (
 | |
| 		spub  [curveKeyLen]byte
 | |
| 		nonce [curveNonceLen]byte
 | |
| 		err   error
 | |
| 	)
 | |
| 	if !bytes.Equal(input[:vlen], []byte(XKeyVersionV1)) {
 | |
| 		return nil, ErrInvalidEncVersion
 | |
| 	}
 | |
| 	copy(nonce[:], input[vlen:vlen+curveNonceLen])
 | |
| 
 | |
| 	if err = decodePubCurveKey(sender, spub[:]); err != nil {
 | |
| 		return nil, ErrInvalidSender
 | |
| 	}
 | |
| 
 | |
| 	decrypted, ok := box.Open(nil, input[vlen+curveNonceLen:], &nonce, &spub, &pair.seed)
 | |
| 	if !ok {
 | |
| 		return nil, ErrCouldNotDecrypt
 | |
| 	}
 | |
| 	return decrypted, nil
 | |
| }
 | |
| 
 | |
| // Wipe will randomize the contents of the secret key
 | |
| func (pair *ckp) Wipe() {
 | |
| 	io.ReadFull(rand.Reader, pair.seed[:])
 | |
| }
 | |
| 
 | |
| func (pair *ckp) Sign(_ []byte) ([]byte, error) {
 | |
| 	return nil, ErrInvalidCurveKeyOperation
 | |
| }
 | |
| 
 | |
| func (pair *ckp) Verify(_ []byte, _ []byte) error {
 | |
| 	return ErrInvalidCurveKeyOperation
 | |
| }
 |