-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
use slogger on local server, add span_id id to logs, add kolide session id to logs #1446
Changes from all commits
ce2b712
d40514e
2079f9d
c279613
933701b
53e0c70
e4c09ed
81a0cb3
7af8aa6
480f4f2
86ec89f
960fe54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,21 +2,22 @@ package localserver | |
|
||
import ( | ||
"bytes" | ||
"context" | ||
"crypto" | ||
"crypto/ecdsa" | ||
"encoding/base64" | ||
"encoding/json" | ||
"fmt" | ||
"io" | ||
"log/slog" | ||
"net/http" | ||
"net/url" | ||
"strings" | ||
"time" | ||
|
||
"github.com/go-kit/kit/log" | ||
"github.com/go-kit/kit/log/level" | ||
"github.com/kolide/krypto" | ||
"github.com/kolide/krypto/pkg/challenge" | ||
"github.com/kolide/launcher/pkg/log/multislogger" | ||
"github.com/kolide/launcher/pkg/traces" | ||
"go.opentelemetry.io/otel/attribute" | ||
"go.opentelemetry.io/otel/codes" | ||
|
@@ -26,6 +27,7 @@ const ( | |
timestampValidityRange = 150 | ||
kolideKryptoEccHeader20230130Value = "2023-01-30" | ||
kolideKryptoHeaderKey = "X-Kolide-Krypto" | ||
kolideSessionIdHeaderKey = "X-Kolide-Session" | ||
) | ||
|
||
type v2CmdRequestType struct { | ||
|
@@ -60,15 +62,15 @@ func (cmdReq v2CmdRequestType) CallbackReq() (*http.Request, error) { | |
type kryptoEcMiddleware struct { | ||
localDbSigner, hardwareSigner crypto.Signer | ||
counterParty ecdsa.PublicKey | ||
logger log.Logger | ||
slogger *slog.Logger | ||
} | ||
|
||
func newKryptoEcMiddleware(logger log.Logger, localDbSigner, hardwareSigner crypto.Signer, counterParty ecdsa.PublicKey) *kryptoEcMiddleware { | ||
func newKryptoEcMiddleware(slogger *slog.Logger, localDbSigner, hardwareSigner crypto.Signer, counterParty ecdsa.PublicKey) *kryptoEcMiddleware { | ||
return &kryptoEcMiddleware{ | ||
localDbSigner: localDbSigner, | ||
hardwareSigner: hardwareSigner, | ||
counterParty: counterParty, | ||
logger: log.With(logger, "keytype", "ec"), | ||
slogger: slogger.With("keytype", "ec"), | ||
} | ||
} | ||
|
||
|
@@ -99,9 +101,13 @@ func (e *kryptoEcMiddleware) sendCallback(req *http.Request, data *callbackDataS | |
if req == nil { | ||
return | ||
} | ||
|
||
b, err := json.Marshal(data) | ||
if err != nil { | ||
level.Debug(e.logger).Log("msg", "unable to marshal callback data", "err", err) | ||
e.slogger.Log(req.Context(), slog.LevelError, | ||
"unable to marshal callback data", | ||
"err", err, | ||
) | ||
} | ||
|
||
req.Body = io.NopCloser(bytes.NewReader(b)) | ||
|
@@ -113,20 +119,28 @@ func (e *kryptoEcMiddleware) sendCallback(req *http.Request, data *callbackDataS | |
|
||
resp, err := client.Do(req) | ||
if err != nil { | ||
level.Debug(e.logger).Log("msg", "got error in callback", "err", err) | ||
e.slogger.Log(req.Context(), slog.LevelError, | ||
"got error in callback", | ||
"err", err, | ||
) | ||
return | ||
} | ||
|
||
if resp != nil && resp.Body != nil { | ||
resp.Body.Close() | ||
} | ||
|
||
level.Debug(e.logger).Log("msg", "Finished callback", "response-status", resp.Status) | ||
e.slogger.Log(req.Context(), slog.LevelDebug, | ||
"finished callback", | ||
"response_status", resp.Status, | ||
) | ||
} | ||
|
||
func (e *kryptoEcMiddleware) Wrap(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
ctx, span := traces.StartSpan(r.Context()) | ||
spanCtx, span := traces.StartSpan(r.Context()) | ||
r = r.WithContext(spanCtx) | ||
|
||
defer span.End() | ||
|
||
if r.Body != nil { | ||
|
@@ -136,14 +150,24 @@ func (e *kryptoEcMiddleware) Wrap(next http.Handler) http.Handler { | |
challengeBox, err := extractChallenge(r) | ||
if err != nil { | ||
traces.SetError(span, err) | ||
level.Debug(e.logger).Log("msg", "failed to extract box from request", "err", err, "path", r.URL.Path, "query_params", r.URL.RawQuery) | ||
e.slogger.Log(r.Context(), slog.LevelDebug, | ||
"failed to extract box from request", | ||
"err", err, | ||
"path", r.URL.Path, | ||
"query_params", r.URL.RawQuery, | ||
) | ||
|
||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
if err := challengeBox.Verify(e.counterParty); err != nil { | ||
traces.SetError(span, err) | ||
level.Debug(e.logger).Log("msg", "unable to verify signature", "err", err) | ||
e.slogger.Log(r.Context(), slog.LevelDebug, | ||
"unable to verify signature", | ||
"err", err, | ||
) | ||
|
||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
|
@@ -153,20 +177,44 @@ func (e *kryptoEcMiddleware) Wrap(next http.Handler) http.Handler { | |
var cmdReq v2CmdRequestType | ||
if err := json.Unmarshal(challengeBox.RequestData(), &cmdReq); err != nil { | ||
traces.SetError(span, err) | ||
level.Debug(e.logger).Log("msg", "unable to unmarshal cmd request", "err", err) | ||
e.slogger.Log(r.Context(), slog.LevelError, | ||
"unable to unmarshal cmd request", | ||
"err", err, | ||
) | ||
|
||
w.WriteHeader(http.StatusUnauthorized) | ||
return | ||
} | ||
|
||
// set the kolide session id if it exists, this also the saml session id | ||
kolideSessionId, ok := cmdReq.CallbackHeaders[kolideSessionIdHeaderKey] | ||
if ok && len(kolideSessionId) > 0 { | ||
span.SetAttributes(attribute.String(kolideSessionIdHeaderKey, kolideSessionId[0])) | ||
r = r.WithContext(context.WithValue(r.Context(), multislogger.KolideSessionIdKey, kolideSessionId[0])) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This line feels awkward. It's pulling in request, context, and multislogger to stash a session key in the request context. I suspect it's correct, I don't know enough to know how to make it cleaner. But it feels awkward. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it would be less awkward if the expected header name was a constant here, it should not overload There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated it so that we define the header key here as a constant. However, we still use the contextKey iota in multislogger to set context value There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yah. That part feels awkward, but I don't know enough to know how to fix it. We are, ultimately, trying to set internal state in context that multislogger uses. So it has to come from there. But we should not stash http headers there |
||
} | ||
|
||
// Setup callback URLs and data. This is a pointer, so it can be adjusted before the defer triggers | ||
callbackData := &callbackDataStruct{ | ||
Time: time.Now().Unix(), | ||
UserAgent: r.Header.Get("User-Agent"), | ||
} | ||
|
||
if callbackReq, err := cmdReq.CallbackReq(); err != nil { | ||
level.Debug(e.logger).Log("msg", "unable to create callback req", "err", err) | ||
e.slogger.Log(r.Context(), slog.LevelError, | ||
"unable to create callback req", | ||
"err", err, | ||
) | ||
} else if callbackReq != nil { | ||
defer func() { go e.sendCallback(callbackReq, callbackData) }() | ||
defer func() { | ||
if kolideSessionId != nil && len(kolideSessionId) > 0 { | ||
callbackReq = callbackReq.WithContext( | ||
// adding the current request context will cause this to be cancelled before it sends | ||
// so just add the session id manually | ||
context.WithValue(callbackReq.Context(), multislogger.KolideSessionIdKey, kolideSessionId[0]), | ||
) | ||
} | ||
go e.sendCallback(callbackReq, callbackData) | ||
}() | ||
} | ||
|
||
// Check the timestamp, this prevents people from saving a challenge and then | ||
|
@@ -175,8 +223,11 @@ func (e *kryptoEcMiddleware) Wrap(next http.Handler) http.Handler { | |
if timestampDelta > timestampValidityRange || timestampDelta < -timestampValidityRange { | ||
span.SetAttributes(attribute.Int64("timestamp_delta", timestampDelta)) | ||
span.SetStatus(codes.Error, "timestamp is out of range") | ||
e.slogger.Log(r.Context(), slog.LevelError, | ||
"timestamp is out of range", | ||
"delta", timestampDelta, | ||
) | ||
|
||
level.Debug(e.logger).Log("msg", "timestamp is out of range", "delta", timestampDelta) | ||
w.WriteHeader(http.StatusUnauthorized) | ||
callbackData.Error = timeOutOfRangeErr | ||
return | ||
|
@@ -190,14 +241,19 @@ func (e *kryptoEcMiddleware) Wrap(next http.Handler) http.Handler { | |
Path: cmdReq.Path, | ||
}, | ||
} | ||
newReq = newReq.WithContext(ctx) // allows the trace to continue to the inner request | ||
|
||
// setting the newReq context to the current request context | ||
// allows the trace to continue to the inner request, | ||
// maintains the same lifetime as the original request, | ||
// allows same ctx values such as session id to be passed to the inner request | ||
newReq = newReq.WithContext(r.Context()) | ||
|
||
// the body of the cmdReq become the body of the next http request | ||
if cmdReq.Body != nil && len(cmdReq.Body) > 0 { | ||
newReq.Body = io.NopCloser(bytes.NewBuffer(cmdReq.Body)) | ||
} | ||
|
||
level.Debug(e.logger).Log("msg", "Successful challenge. Proxying") | ||
e.slogger.Log(r.Context(), slog.LevelDebug, "successful challenge, proxying") | ||
span.AddEvent("challenge_success") | ||
|
||
// bhr contains the data returned by the request defined above | ||
|
@@ -216,7 +272,10 @@ func (e *kryptoEcMiddleware) Wrap(next http.Handler) http.Handler { | |
|
||
if err != nil { | ||
traces.SetError(span, err) | ||
level.Debug(e.logger).Log("msg", "failed to respond", "err", err) | ||
e.slogger.Log(r.Context(), slog.LevelError, | ||
"failed to respond", | ||
"err", err, | ||
) | ||
w.WriteHeader(http.StatusUnauthorized) | ||
callbackData.Error = responseFailureErr | ||
return | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure kolide session id and saml session id are the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ask Chris and he confirmed it was the saml id
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They might be the same now, but I'm not sure they'll be the same later. I'm suggesting we drop the comment about saml session id.