diff --git a/cmd/sansshell-server/default-policy.rego b/cmd/sansshell-server/default-policy.rego index ec0fec81..c83bda7a 100644 --- a/cmd/sansshell-server/default-policy.rego +++ b/cmd/sansshell-server/default-policy.rego @@ -85,6 +85,10 @@ allow { input.method = "/SysInfo.SysInfo/Dmesg" } +allow { + input.method = "/TLSInfo.TLSInfo/GetTLSCertificate" +} + # Allow anything from a proxy allow { input.peer.principal.id = "proxy" diff --git a/services/tlsinfo/client/client.go b/services/tlsinfo/client/client.go index 44dceff5..24669b00 100644 --- a/services/tlsinfo/client/client.go +++ b/services/tlsinfo/client/client.go @@ -19,6 +19,7 @@ package client import ( "context" + "encoding/pem" "flag" "fmt" "os" @@ -27,6 +28,7 @@ import ( "github.com/Snowflake-Labs/sansshell/client" pb "github.com/Snowflake-Labs/sansshell/services/tlsinfo" "github.com/Snowflake-Labs/sansshell/services/util" + cliUtils "github.com/Snowflake-Labs/sansshell/services/util/cli" "github.com/google/subcommands" ) @@ -62,6 +64,7 @@ func (c *tlsInfoCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...inter type getCertsCmd struct { serverName string insecureSkipVerify bool + printPEM bool } func (*getCertsCmd) Name() string { return "get-certs" } @@ -79,6 +82,7 @@ func (c *getCertsCmd) Usage() string { } func (c *getCertsCmd) SetFlags(f *flag.FlagSet) { f.BoolVar(&c.insecureSkipVerify, "insecure-skip-verify", false, "If true, will skip verification of server's certificate chain and host name") + f.BoolVar(&c.printPEM, "pem", false, "Print certificates in PEM format") f.StringVar(&c.serverName, "server-name", "", "server-name is used to specify the Server Name Indication (SNI) during the TLS handshake. It allows client to indicate which hostname it's trying to connect to.") } @@ -103,20 +107,38 @@ func (c *getCertsCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...inte foundErr := false for r := range respChan { - fmt.Fprintf(state.Out[r.Index], "Target %s result:\n", r.Target) + targetLogger := cliUtils.NewStyledCliLogger(state.Out[r.Index], state.Err[r.Index], &cliUtils.CliLoggerOptions{ + ApplyStylingForErr: util.IsStreamToTerminal(state.Err[r.Index]), + ApplyStylingForOut: util.IsStreamToTerminal(state.Out[r.Index]), + }) + if r.Error != nil { - fmt.Fprintf(state.Err[r.Index], "Get TLS certificates failure: %v\n", r.Error) + targetLogger.Errorf("Get TLS certificates failure: %v\n", r.Error) foundErr = true continue } for i, cert := range r.Resp.Certificates { - fmt.Fprintf(state.Out[r.Index], "---Server Certificate--- %d\n", i) - fmt.Fprintf(state.Out[r.Index], "Issuer: %v\n", cert.Issuer) - fmt.Fprintf(state.Out[r.Index], "Subject: %v\n", cert.Subject) - fmt.Fprintf(state.Out[r.Index], "NotBefore: %s\n", time.Unix(cert.NotBefore, 0)) - fmt.Fprintf(state.Out[r.Index], "NotAfter: %s\n", time.Unix(cert.NotAfter, 0)) - fmt.Fprintf(state.Out[r.Index], "DNS Names: %v\n", cert.DnsNames) - fmt.Fprintf(state.Out[r.Index], "IP Addresses: %v\n\n", cert.IpAddresses) + if c.printPEM { + if len(cert.Raw) == 0 { + targetLogger.Errorf("no raw cert available for %v, sansshell-server may be too old to return cert info\n", cert.Subject) + } else { + err := pem.Encode(state.Out[r.Index], &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + }) + if err != nil { + targetLogger.Errorf("unable to encode cert as pem: %v\n", cert.Raw) + } + } + } else { + targetLogger.Infof("---Server Certificate--- %d\n", i) + targetLogger.Infof("Issuer: %v\n", cliUtils.Colorize(cliUtils.GreenText, cert.Issuer)) + targetLogger.Infof("Subject: %v\n", cliUtils.Colorize(cliUtils.GreenText, cert.Subject)) + targetLogger.Infof("NotBefore: %s\n", cliUtils.Colorize(cliUtils.GreenText, time.Unix(cert.NotBefore, 0))) + targetLogger.Infof("NotAfter: %s\n", cliUtils.Colorize(cliUtils.GreenText, time.Unix(cert.NotAfter, 0))) + targetLogger.Infof("DNS Names: %v\n", cliUtils.Colorize(cliUtils.GreenText, cert.DnsNames)) + targetLogger.Infof("IP Addresses: %v\n\n", cliUtils.Colorize(cliUtils.GreenText, cert.IpAddresses)) + } } } diff --git a/services/tlsinfo/server/server.go b/services/tlsinfo/server/server.go index c41e55be..5b4d4f15 100644 --- a/services/tlsinfo/server/server.go +++ b/services/tlsinfo/server/server.go @@ -73,6 +73,7 @@ func (s *server) GetTLSCertificate(ctx context.Context, req *pb.TLSCertificateRe NotBefore: cert.NotBefore.Unix(), NotAfter: cert.NotAfter.Unix(), DnsNames: cert.DNSNames, + Raw: cert.Raw, } for _, ipAddr := range cert.IPAddresses { protoCert.IpAddresses = append(protoCert.IpAddresses, ipAddr.String()) diff --git a/services/tlsinfo/tlsinfo.pb.go b/services/tlsinfo/tlsinfo.pb.go index f4a3d4fe..b2b6ca3d 100644 --- a/services/tlsinfo/tlsinfo.pb.go +++ b/services/tlsinfo/tlsinfo.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.32.0 -// protoc v5.26.1 +// protoc-gen-go v1.34.2 +// protoc v3.19.6 // source: tlsinfo.proto package tlsinfo @@ -109,6 +109,7 @@ type TLSCertificate struct { NotAfter int64 `protobuf:"varint,4,opt,name=not_after,json=notAfter,proto3" json:"not_after,omitempty"` DnsNames []string `protobuf:"bytes,5,rep,name=dns_names,json=dnsNames,proto3" json:"dns_names,omitempty"` IpAddresses []string `protobuf:"bytes,6,rep,name=ip_addresses,json=ipAddresses,proto3" json:"ip_addresses,omitempty"` + Raw []byte `protobuf:"bytes,7,opt,name=raw,proto3" json:"raw,omitempty"` // Complete ASN.1 DER content (certificate, signature algorithm and signature) } func (x *TLSCertificate) Reset() { @@ -185,6 +186,13 @@ func (x *TLSCertificate) GetIpAddresses() []string { return nil } +func (x *TLSCertificate) GetRaw() []byte { + if x != nil { + return x.Raw + } + return nil +} + type TLSCertificateChain struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -245,7 +253,7 @@ var file_tlsinfo_proto_rawDesc = []byte{ 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, 0x72, 0x65, 0x5f, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x6e, 0x73, 0x65, 0x63, 0x75, - 0x72, 0x65, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x22, 0xbe, 0x01, 0x0a, + 0x72, 0x65, 0x53, 0x6b, 0x69, 0x70, 0x56, 0x65, 0x72, 0x69, 0x66, 0x79, 0x22, 0xd0, 0x01, 0x0a, 0x0e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x75, 0x62, 0x6a, 0x65, @@ -257,22 +265,23 @@ var file_tlsinfo_proto_rawDesc = []byte{ 0x09, 0x64, 0x6e, 0x73, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x64, 0x6e, 0x73, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x69, 0x70, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x09, - 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x22, 0x52, 0x0a, - 0x13, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, - 0x68, 0x61, 0x69, 0x6e, 0x12, 0x3b, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x54, 0x4c, 0x53, - 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, - 0x73, 0x32, 0x5c, 0x0a, 0x07, 0x54, 0x4c, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x51, 0x0a, 0x11, - 0x47, 0x65, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x65, 0x12, 0x1e, 0x2e, 0x54, 0x4c, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x4c, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x42, - 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x53, 0x6e, - 0x6f, 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x2d, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x73, 0x61, 0x6e, - 0x73, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x2f, 0x74, 0x6c, 0x73, 0x69, 0x6e, 0x66, 0x6f, 0x62, 0x06, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x52, 0x0b, 0x69, 0x70, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x10, 0x0a, + 0x03, 0x72, 0x61, 0x77, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x72, 0x61, 0x77, 0x22, + 0x52, 0x0a, 0x13, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, + 0x65, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x12, 0x3b, 0x0a, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x54, + 0x4c, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, + 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x0c, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x65, 0x73, 0x32, 0x5c, 0x0a, 0x07, 0x54, 0x4c, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x51, + 0x0a, 0x11, 0x47, 0x65, 0x74, 0x54, 0x4c, 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x65, 0x12, 0x1e, 0x2e, 0x54, 0x4c, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x54, 0x4c, + 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x54, 0x4c, 0x53, 0x49, 0x6e, 0x66, 0x6f, 0x2e, 0x54, 0x4c, + 0x53, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x43, 0x68, 0x61, 0x69, + 0x6e, 0x42, 0x2d, 0x5a, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x53, 0x6e, 0x6f, 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x2d, 0x4c, 0x61, 0x62, 0x73, 0x2f, 0x73, + 0x61, 0x6e, 0x73, 0x73, 0x68, 0x65, 0x6c, 0x6c, 0x2f, 0x74, 0x6c, 0x73, 0x69, 0x6e, 0x66, 0x6f, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -288,7 +297,7 @@ func file_tlsinfo_proto_rawDescGZIP() []byte { } var file_tlsinfo_proto_msgTypes = make([]protoimpl.MessageInfo, 3) -var file_tlsinfo_proto_goTypes = []interface{}{ +var file_tlsinfo_proto_goTypes = []any{ (*TLSCertificateRequest)(nil), // 0: TLSInfo.TLSCertificateRequest (*TLSCertificate)(nil), // 1: TLSInfo.TLSCertificate (*TLSCertificateChain)(nil), // 2: TLSInfo.TLSCertificateChain @@ -310,7 +319,7 @@ func file_tlsinfo_proto_init() { return } if !protoimpl.UnsafeEnabled { - file_tlsinfo_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + file_tlsinfo_proto_msgTypes[0].Exporter = func(v any, i int) any { switch v := v.(*TLSCertificateRequest); i { case 0: return &v.state @@ -322,7 +331,7 @@ func file_tlsinfo_proto_init() { return nil } } - file_tlsinfo_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + file_tlsinfo_proto_msgTypes[1].Exporter = func(v any, i int) any { switch v := v.(*TLSCertificate); i { case 0: return &v.state @@ -334,7 +343,7 @@ func file_tlsinfo_proto_init() { return nil } } - file_tlsinfo_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + file_tlsinfo_proto_msgTypes[2].Exporter = func(v any, i int) any { switch v := v.(*TLSCertificateChain); i { case 0: return &v.state diff --git a/services/tlsinfo/tlsinfo.proto b/services/tlsinfo/tlsinfo.proto index 82ce533b..1dcf73ad 100644 --- a/services/tlsinfo/tlsinfo.proto +++ b/services/tlsinfo/tlsinfo.proto @@ -39,6 +39,7 @@ message TLSCertificate { int64 not_after = 4; repeated string dns_names = 5; repeated string ip_addresses = 6; + bytes raw = 7; // Complete ASN.1 DER content (certificate, signature algorithm and signature) } message TLSCertificateChain { diff --git a/services/tlsinfo/tlsinfo_grpc.pb.go b/services/tlsinfo/tlsinfo_grpc.pb.go index 7fee2be8..3ae76e74 100644 --- a/services/tlsinfo/tlsinfo_grpc.pb.go +++ b/services/tlsinfo/tlsinfo_grpc.pb.go @@ -15,8 +15,8 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.3.0 -// - protoc v5.26.1 +// - protoc-gen-go-grpc v1.4.0 +// - protoc v3.19.6 // source: tlsinfo.proto package tlsinfo @@ -30,8 +30,8 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +// Requires gRPC-Go v1.62.0 or later. +const _ = grpc.SupportPackageIsVersion8 const ( TLSInfo_GetTLSCertificate_FullMethodName = "/TLSInfo.TLSInfo/GetTLSCertificate" @@ -40,6 +40,8 @@ const ( // TLSInfoClient is the client API for TLSInfo service. // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +// +// The TLSInfo service definition type TLSInfoClient interface { // Retrieves details of the TLS certificate chain from a specified server GetTLSCertificate(ctx context.Context, in *TLSCertificateRequest, opts ...grpc.CallOption) (*TLSCertificateChain, error) @@ -54,8 +56,9 @@ func NewTLSInfoClient(cc grpc.ClientConnInterface) TLSInfoClient { } func (c *tLSInfoClient) GetTLSCertificate(ctx context.Context, in *TLSCertificateRequest, opts ...grpc.CallOption) (*TLSCertificateChain, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(TLSCertificateChain) - err := c.cc.Invoke(ctx, TLSInfo_GetTLSCertificate_FullMethodName, in, out, opts...) + err := c.cc.Invoke(ctx, TLSInfo_GetTLSCertificate_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -65,6 +68,8 @@ func (c *tLSInfoClient) GetTLSCertificate(ctx context.Context, in *TLSCertificat // TLSInfoServer is the server API for TLSInfo service. // All implementations should embed UnimplementedTLSInfoServer // for forward compatibility +// +// The TLSInfo service definition type TLSInfoServer interface { // Retrieves details of the TLS certificate chain from a specified server GetTLSCertificate(context.Context, *TLSCertificateRequest) (*TLSCertificateChain, error) diff --git a/services/util/cli/styled-cli-logger.go b/services/util/cli/styled-cli-logger.go index 324c8b5e..28bd1413 100644 --- a/services/util/cli/styled-cli-logger.go +++ b/services/util/cli/styled-cli-logger.go @@ -145,9 +145,9 @@ type StyledText interface { String() string } -func Colorize(color ColorCode, text string) StyledText { +func Colorize(color ColorCode, a any) StyledText { return &styledText{ - text: text, + text: fmt.Sprint(a), colorCode: color, } }