-
Notifications
You must be signed in to change notification settings - Fork 186
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
ig/gadgetctl: add TLS support #2848
base: main
Are you sure you want to change the base?
Conversation
Signed-off-by: Michael Friese <mfriese@microsoft.com>
Signed-off-by: Michael Friese <mfriese@microsoft.com>
Signed-off-by: Michael Friese <mfriese@microsoft.com>
a6d62ec
to
3998aa7
Compare
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.
Hi!
I have some initial comments, but this is a great addition!
Best regards.
cert, err := tls.LoadX509KeyPair(tlsCert, tlsKey) | ||
if err != nil { | ||
return nil, fmt.Errorf("loading TLS keypair: %w", err) | ||
} | ||
|
||
ca := x509.NewCertPool() | ||
caBytes, err := os.ReadFile(tlsCA) | ||
if err != nil { | ||
return nil, fmt.Errorf("loading client CA certificate: %w", err) | ||
} | ||
if ok := ca.AppendCertsFromPEM(caBytes); !ok { | ||
return nil, errors.New("failed to parse client CA certificate") | ||
} | ||
|
||
purl, _ := url.Parse(target.addressOrPod) | ||
|
||
tlsConfig := &tls.Config{ | ||
ServerName: purl.Hostname(), | ||
Certificates: []tls.Certificate{cert}, | ||
RootCAs: ca, | ||
} |
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.
|
||
options = append(options, grpc.Creds(credentials.NewTLS(tlsConfig))) | ||
} else { | ||
log.Warnf("no TLS configuration provided, running in insecure mode") |
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.
This is too vague:
log.Warnf("no TLS configuration provided, running in insecure mode") | |
log.Warnf("no TLS configuration provided, communication between daemon and CLI will not be ciphered") |
ParamTLSKey = "tls-key" | ||
ParamTLSCert = "tls-cert" | ||
ParamTLSServerCA = "tls-server-ca" | ||
ParamTLSServerName = "tls-server-name" |
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.
Cannot you use these in the CLI too?
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.
You mean sharing the consts? They would only share ParamTLSKey
and ParamTLSCert
, so I'd rather keep them local to the pkg using them.
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.
It is still two you can share.
Moreover, if some are specific to this file, they should not be public.
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.
Regarding keeping them local but exported:
Those Params can also be set when using the IG Go API for connecting to a remote - in that case IMHO it already helps if you're typing grpc.Param
(when using that runtime) and your IDE can show you the available options. We've done the same with params for other operators (and should sooner or later do it for the remaining ones - but this is still in progress and discussion (also in regards to where to put their documentation)), so you can more easily find and use them.
Reusing them when being local to that package, though, would mean to introde a dependency between the server component and the client - which I think isn't a good option.
Also, even if the strings match, the meaning is different - in one case it's the client's keypair, in the other it's the servers'. Their meaning is implied from the context they're used in.
But I'll add dedicated consts to the cmd/ig
package as well to keep it a bit cleaner at least.
/cc @mauriciovasquezbernal regarding the handling of Params
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.
Regarding keeping them local but exported: Those Params can also be set when using the IG Go API for connecting to a remote - in that case IMHO it already helps if you're typing
grpc.Param
(when using that runtime) and your IDE can show you the available options. We've done the same with params for other operators (and should sooner or later do it for the remaining ones - but this is still in progress and discussion (also in regards to where to put their documentation)), so you can more easily find and use them.Reusing them when being local to that package, though, would mean to introde a dependency between the server component and the client - which I think isn't a good option.
Indeed, let's avoid this.
Also, even if the strings match, the meaning is different - in one case it's the client's keypair, in the other it's the servers'. Their meaning is implied from the context they're used in.
Hum...
I rather find this confusing, but OK.
But I'll add dedicated consts to the
cmd/ig
package as well to keep it a bit cleaner at least./cc @mauriciovasquezbernal regarding the handling of Params
if ok { | ||
subject := tlsInfo.State.VerifiedChains[0][0].Subject | ||
s.logger.Infof("[%s] GetGadgetInfo(%q)", subject, req.ImageName) | ||
} |
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.
Should we explode if !ok
?
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.
No - it'll always be ok for TLS connections, as they're always verified. This will only be !ok if the connection is insecure (which is a valid and right now the default use case.)
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.
What if it is no OK for whatsoever reason?
This is a bit paranoid, but I definitely would like to have a case where people think we are using TLS and we just end not using it.
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.
Hmm... It's pretty much a rabbit hole with the question "To what degree do we trust the imported fundamental packages like gRPC to work"? But if there's ever a good time to be paranoid, this might be it - hehe. I'll add that check.
if ok { | ||
subject := tlsInfo.State.VerifiedChains[0][0].Subject | ||
s.logger.Infof("[%s] RunGadget(%q)", subject, ociRequest.ImageName) | ||
} |
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.
Same.
By the way, the code can be factorized.
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.
It's looking very good. Some initial comments.
I think there is missing a openssl ecparam -name prime256v1 -genkey -noout -out server.key
step in the description.
{ | ||
Key: ParamTLSKey, | ||
Description: "TLS client key", | ||
DefaultValue: "", |
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.
nit: remove? (same in other places)
} | ||
|
||
if ok := ca.AppendCertsFromPEM(caBytes); !ok { | ||
return errors.New("failed to parse client CA certificate") |
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.
parsing ...?
(Not sure why semgrep doesn't complain about it)
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 wondered the same: it only complains when wrapping errors - this is the last message in the chain, so it's fine. (e.g. "doing this: doing that: doing another thing: failed to blah")
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.
Hmm, just ran into a similar situation on another PR without wrapping and it complained. 🤷
daemonCmd.PersistentFlags().StringVarP( | ||
&serverKey, | ||
"tls-key", | ||
"", | ||
"", | ||
"Path to tls key file") | ||
|
||
daemonCmd.PersistentFlags().StringVarP( | ||
&serverCert, | ||
"tls-cert", | ||
"", | ||
"", | ||
"Path to tls cert file") | ||
|
||
daemonCmd.PersistentFlags().StringVarP( | ||
&clientCA, | ||
"tls-client-ca", | ||
"", | ||
"", | ||
"Path to CA certificate for client validation") | ||
|
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.
Use StringVar
to avoid passing an extra empty string.
|
||
var options []grpc.ServerOption | ||
|
||
if serverKey != "" || serverCert != "" || clientCA != "" { |
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 think we should we add a more specific validation logic to fail if "one but not all" flags are defined instead of relying on the functions below failing. It'll also print a better error message to the user.
grpc.WithReturnConnectionError(), | ||
} | ||
|
||
if tlsKey, tlsCert, tlsCA := r.globalParams.Get(ParamTLSKey).String(), r.globalParams.Get(ParamTLSCert).String(), r.globalParams.Get(ParamTLSServerCA).String(); tlsKey != "" || tlsCert != "" || tlsCA != "" { |
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 think this sets the new longest line record.
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.
So where is my trophy, then? 😆
|
||
options = append(options, grpc.Creds(credentials.NewTLS(tlsConfig))) | ||
} else { | ||
log.Warnf("no TLS configuration provided, running in insecure mode") |
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.
Do we need to print this message even when unix sockets are used? I'd argue it's not needed as in that case the client needs access to the unix socket file to contact the server.
return nil, errors.New("failed to parse client CA certificate") | ||
} | ||
|
||
purl, _ := url.Parse(target.addressOrPod) |
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 think we need to check the return code. Running the following makes it explode:
$ gadgetctl run trace_open:latest --remote-address tcp://127.0.0.1:1234 --tls-cert client1.crt --tls-key client1.key --tls-server-ca ca.crt --tls-server-name local
INFO[0000] Experimental features enabled
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x28 pc=0x2c373d0]
goroutine 1 [running]:
net/url.(*URL).Hostname(...)
/usr/local/go/src/net/url/url.go:1151
github.com/inspektor-gadget/inspektor-gadget/pkg/runtime/grpc.(*Runtime).dialContext(0xc0002a2b70, {0x4018548, 0xc000276b90}, {{0x7ffc9f23228b?, 0xc0006c3850?}, {0x7ffc9f23228b?, 0x14a1f9b?}}, 0x12a05f200)
/home/mauriciov/kinvolk/ebpf/inspektor-gadget/pkg/runtime/grpc/grpc-runtime.go:422 +0x550
github.com/inspektor-gadget/inspektor-gadget/pkg/runtime/grpc.(*Runtime).getConnToRandomTarget(0xc0002a2b70, {0x4018548, 0xc000276b90}, 0x17?)
/home/mauriciov/kinvolk/ebpf/inspektor-gadget/pkg/runtime/grpc/grpc-runtime.go:347 +0x216
github.com/inspektor-gadget/inspektor-gadget/pkg/runtime/grpc.(*Runtime).GetGadgetInfo(0xc0002a2b70, {0x4065ce0, 0xc0005fd500}, 0xd?, 0xc0005618c0)
/home/mauriciov/kinvolk/ebpf/inspektor-gadget/pkg/runtime/grpc/oci.go:39 +0x17d
github.com/inspektor-gadget/inspektor-gadget/cmd/common.NewRunCommand.func1(0xc0004f4f08, {0xc00045fd90, 0xb, 0xb})
/home/mauriciov/kinvolk/ebpf/inspektor-gadget/cmd/common/oci.go:107 +0x4e3
github.com/spf13/cobra.(*Command).execute(0xc0004f4f08, {0xc00045fd90, 0xb, 0xb})
/home/mauriciov/go/pkg/mod/github.com/spf13/cobra@v1.8.0/command.go:968 +0xa44
github.com/spf13/cobra.(*Command).ExecuteC(0xc000160f08)
/home/mauriciov/go/pkg/mod/github.com/spf13/cobra@v1.8.0/command.go:1115 +0x3ff
github.com/spf13/cobra.(*Command).Execute(...)
/home/mauriciov/go/pkg/mod/github.com/spf13/cobra@v1.8.0/command.go:1039
main.main()
/home/mauriciov/kinvolk/ebpf/inspektor-gadget/cmd/gadgetctl/main.go:92 +0x745
$
``
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.
Hmm yeah, I should definitely add that - it shouldn't fail for the given address, nevertheless.
{ | ||
Key: ParamTLSKey, | ||
Description: "TLS client key", | ||
DefaultValue: "", | ||
TypeHint: params.TypeString, | ||
}, | ||
{ | ||
Key: ParamTLSCert, | ||
Description: "TLS client certificate", | ||
DefaultValue: "", | ||
TypeHint: params.TypeString, | ||
}, | ||
{ | ||
Key: ParamTLSServerCA, | ||
Description: "TLS server CA certificate", | ||
DefaultValue: "", | ||
TypeHint: params.TypeString, | ||
}, | ||
{ | ||
Key: ParamTLSServerName, | ||
Description: "override TLS server name (if omitted, using target server name)", | ||
DefaultValue: "", | ||
TypeHint: params.TypeString, | ||
}, |
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.
Shouldn't they be moved under case ConnectionModeDirect
to avoid showing them in the kubernetes case?
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.
At least until we also support it for ig-k8s, yes.
This PR adds support for TLS encryption & authentication for
ig
running as daemon andgadgetctl
to connect to it. It supports mutual verification by checking certificates against certificate authorities.Here's how to test it (this short guide to create a CA and server/client keys is just an example!!!):
In this example both client and server are using the same CA - this is however not necessary.
Copy files ca.crt, server.key & server.cert to
ig
and another copy of ca.crt, client1.key & client1.cert togadgetctl
and run as follows:(Replace 1000 as
--group
with your preferred group id to be able to connect from agadgetctl
not running as root.)Since we're connecting locally, we have to specify the
--tls-server-name
we used as SAN when creating the key pairs above.The server log will now contain information like this: