Skip to content

Commit 7665f81

Browse files
committed
Operations endpoints (issue #45)
Signed-off-by: Genady Gurevich <genadyg@il.ibm.com>
1 parent df39a69 commit 7665f81

3,880 files changed

Lines changed: 1306461 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

common/fabxhttp/server.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package fabxhttp
8+
9+
import (
10+
"context"
11+
"crypto/tls"
12+
"net"
13+
"net/http"
14+
"os"
15+
"time"
16+
17+
"github.com/hyperledger/fabric-lib-go/common/flogging"
18+
19+
"github.com/hyperledger/fabric-x-common/common/middleware"
20+
"github.com/hyperledger/fabric-x-common/common/util"
21+
)
22+
23+
// Logger defines the logging interface for the HTTP server.
24+
type Logger interface {
25+
Warn(args ...any)
26+
Warnf(template string, args ...any)
27+
}
28+
29+
// Options contains configuration options for the HTTP server.
30+
type Options struct {
31+
Logger Logger
32+
ListenAddress string
33+
TLS TLS
34+
}
35+
36+
// Server represents an HTTP server with TLS support and middleware capabilities.
37+
type Server struct {
38+
logger Logger
39+
options Options
40+
httpServer *http.Server
41+
mux *http.ServeMux
42+
addr string
43+
}
44+
45+
// NewServer creates a new HTTP server with the provided options.
46+
func NewServer(o Options) *Server {
47+
logger := o.Logger
48+
if logger == nil {
49+
logger = flogging.MustGetLogger("fabhttp")
50+
}
51+
52+
server := &Server{
53+
logger: logger,
54+
options: o,
55+
}
56+
57+
server.initializeServer()
58+
59+
return server
60+
}
61+
62+
// Run starts the server and blocks until a signal is received, then stops the server.
63+
func (s *Server) Run(signals <-chan os.Signal, ready chan<- struct{}) error {
64+
err := s.Start()
65+
if err != nil {
66+
return err
67+
}
68+
69+
close(ready)
70+
71+
<-signals
72+
return s.Stop()
73+
}
74+
75+
// Start begins listening and serving HTTP requests in a goroutine.
76+
func (s *Server) Start() error {
77+
listener, err := s.Listen()
78+
if err != nil {
79+
return err
80+
}
81+
s.addr = listener.Addr().String()
82+
83+
go func() {
84+
if err := s.httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
85+
s.logger.Warnf("HTTP server stopped with error: %v", err)
86+
}
87+
}()
88+
89+
return nil
90+
}
91+
92+
// Stop gracefully shuts down the server with a timeout.
93+
func (s *Server) Stop() error {
94+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
95+
defer cancel()
96+
97+
return s.httpServer.Shutdown(ctx)
98+
}
99+
100+
func (s *Server) initializeServer() {
101+
s.mux = http.NewServeMux()
102+
s.httpServer = &http.Server{
103+
Addr: s.options.ListenAddress,
104+
Handler: s.mux,
105+
ReadTimeout: 10 * time.Second,
106+
WriteTimeout: 2 * time.Minute,
107+
}
108+
}
109+
110+
// HandlerChain wraps the provided handler with middleware based on security requirements.
111+
func (s *Server) HandlerChain(h http.Handler, secure bool) http.Handler {
112+
if secure {
113+
return middleware.NewChain(middleware.RequireCert(), middleware.WithRequestID(util.GenerateUUID)).Handler(h)
114+
}
115+
return middleware.NewChain(middleware.WithRequestID(util.GenerateUUID)).Handler(h)
116+
}
117+
118+
// RegisterHandler registers into the ServeMux a handler chain that borrows
119+
// its security properties from the fabhttp.Server. This method is thread
120+
// safe because ServeMux.Handle() is thread safe, and options are immutable.
121+
// This method can be called either before or after Server.Start(). If the
122+
// pattern exists the method panics.
123+
func (s *Server) RegisterHandler(pattern string, handler http.Handler, secure bool) {
124+
s.mux.Handle(
125+
pattern,
126+
s.HandlerChain(
127+
handler,
128+
secure,
129+
),
130+
)
131+
}
132+
133+
// Listen creates a network listener with optional TLS configuration.
134+
func (s *Server) Listen() (net.Listener, error) {
135+
listener, err := net.Listen("tcp", s.options.ListenAddress)
136+
if err != nil {
137+
return nil, err
138+
}
139+
tlsConfig, err := s.options.TLS.Config()
140+
if err != nil {
141+
return nil, err
142+
}
143+
if tlsConfig != nil {
144+
listener = tls.NewListener(listener, tlsConfig)
145+
}
146+
return listener, nil
147+
}
148+
149+
// Addr returns the server's listening address.
150+
func (s *Server) Addr() string {
151+
return s.addr
152+
}
153+
154+
// Log logs a warning message with the provided key-value pairs.
155+
func (s *Server) Log(keyvals ...any) error {
156+
s.logger.Warn(keyvals...)
157+
return nil
158+
}

common/fabxhttp/tls.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package fabxhttp
8+
9+
import (
10+
"crypto/tls"
11+
"crypto/x509"
12+
"os"
13+
)
14+
15+
// TLS contains TLS configuration options for the HTTP server.
16+
type TLS struct {
17+
Enabled bool
18+
CertFile string
19+
KeyFile string
20+
ClientCertRequired bool
21+
ClientCACertFiles []string
22+
}
23+
24+
// strong TLS cipher suites
25+
var DefaultTLSCipherSuites = []uint16{
26+
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
27+
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
28+
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
29+
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
30+
tls.TLS_RSA_WITH_AES_128_GCM_SHA256,
31+
tls.TLS_RSA_WITH_AES_256_GCM_SHA384,
32+
}
33+
34+
func (t TLS) Config() (*tls.Config, error) {
35+
var tlsConfig *tls.Config
36+
37+
if t.Enabled {
38+
cert, err := tls.LoadX509KeyPair(t.CertFile, t.KeyFile)
39+
if err != nil {
40+
return nil, err
41+
}
42+
caCertPool := x509.NewCertPool()
43+
for _, caPath := range t.ClientCACertFiles {
44+
caPem, err := os.ReadFile(caPath)
45+
if err != nil {
46+
return nil, err
47+
}
48+
caCertPool.AppendCertsFromPEM(caPem)
49+
}
50+
tlsConfig = &tls.Config{
51+
MinVersion: tls.VersionTLS12,
52+
Certificates: []tls.Certificate{cert},
53+
CipherSuites: DefaultTLSCipherSuites,
54+
ClientCAs: caCertPool,
55+
}
56+
if t.ClientCertRequired {
57+
tlsConfig.ClientAuth = tls.RequireAndVerifyClientCert
58+
} else {
59+
tlsConfig.ClientAuth = tls.VerifyClientCertIfGiven
60+
}
61+
}
62+
63+
return tlsConfig, nil
64+
}

common/middleware/chain.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package middleware
8+
9+
import (
10+
"net/http"
11+
12+
"github.com/hyperledger/fabric-lib-go/common/flogging"
13+
)
14+
15+
var logger = flogging.MustGetLogger("middleware")
16+
17+
type Middleware func(http.Handler) http.Handler
18+
19+
// A Chain is a middleware chain use for http request processing.
20+
type Chain struct {
21+
mw []Middleware
22+
}
23+
24+
// NewChain creates a new Middleware chain. The chain will call the Middleware
25+
// in the order provided.
26+
func NewChain(middlewares ...Middleware) Chain {
27+
return Chain{
28+
mw: append([]Middleware{}, middlewares...),
29+
}
30+
}
31+
32+
// Handler returns an http.Handler for this chain.
33+
func (c Chain) Handler(h http.Handler) http.Handler {
34+
if h == nil {
35+
h = http.DefaultServeMux
36+
}
37+
38+
for i := len(c.mw) - 1; i >= 0; i-- {
39+
h = c.mw[i](h)
40+
}
41+
return h
42+
}

common/middleware/request_id.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package middleware
8+
9+
import (
10+
"context"
11+
"net/http"
12+
)
13+
14+
var requestIDKey = requestIDKeyType{}
15+
16+
type requestIDKeyType struct{}
17+
18+
func RequestID(ctx context.Context) string {
19+
if reqID, ok := ctx.Value(requestIDKey).(string); ok {
20+
return reqID
21+
}
22+
return "unknown"
23+
}
24+
25+
type GenerateIDFunc func() string
26+
27+
type requestID struct {
28+
generateID GenerateIDFunc
29+
next http.Handler
30+
}
31+
32+
func WithRequestID(generator GenerateIDFunc) Middleware {
33+
return func(next http.Handler) http.Handler {
34+
return &requestID{next: next, generateID: generator}
35+
}
36+
}
37+
38+
func (r *requestID) ServeHTTP(w http.ResponseWriter, req *http.Request) {
39+
reqID := req.Header.Get("X-Request-Id")
40+
if reqID == "" {
41+
reqID = r.generateID()
42+
req.Header.Set("X-Request-Id", reqID)
43+
}
44+
45+
ctx := context.WithValue(req.Context(), requestIDKey, reqID)
46+
req = req.WithContext(ctx)
47+
48+
w.Header().Add("X-Request-Id", reqID)
49+
50+
r.next.ServeHTTP(w, req)
51+
}

common/middleware/require_cert.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
Copyright IBM Corp. All Rights Reserved.
3+
4+
SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
package middleware
8+
9+
import (
10+
"net/http"
11+
)
12+
13+
type requireCert struct {
14+
next http.Handler
15+
}
16+
17+
// RequireCert is used to ensure that a verified TLS client certificate was
18+
// used for authentication.
19+
func RequireCert() Middleware {
20+
return func(next http.Handler) http.Handler {
21+
return &requireCert{next: next}
22+
}
23+
}
24+
25+
func (r *requireCert) ServeHTTP(w http.ResponseWriter, req *http.Request) {
26+
switch {
27+
case req.TLS == nil:
28+
fallthrough
29+
case len(req.TLS.VerifiedChains) == 0:
30+
fallthrough
31+
case len(req.TLS.VerifiedChains[0]) == 0:
32+
logger.Warnw(
33+
"Client request not authorized, client must pass a valid client certificate for this operation",
34+
"URL", req.URL,
35+
"Method", req.Method,
36+
"RemoteAddr", req.RemoteAddr,
37+
)
38+
w.WriteHeader(http.StatusUnauthorized)
39+
default:
40+
r.next.ServeHTTP(w, req)
41+
}
42+
}

0 commit comments

Comments
 (0)