diff --git a/auth.go b/auth.go index 435c94b1..45f77e62 100644 --- a/auth.go +++ b/auth.go @@ -48,6 +48,20 @@ func (auth *AMQPlainAuth) Response() string { return fmt.Sprintf("LOGIN:%sPASSWORD:%s", auth.Username, auth.Password) } +// ExternalAuth for RabbitMQ-auth-mechanism-ssl. +type ExternalAuth struct { +} + +// Mechanism returns "EXTERNAL" +func (me *ExternalAuth) Mechanism() string { + return "EXTERNAL" +} + +// Response returns zero ? +func (me *ExternalAuth) Response() string { + return "\000*\000*" +} + // Finds the first mechanism preferred by the client that the server supports. func pickSASLMechanism(client []Authentication, serverMechanisms []string) (auth Authentication, ok bool) { for _, auth = range client { diff --git a/connection.go b/connection.go index 252852e8..38e3ab0f 100644 --- a/connection.go +++ b/connection.go @@ -130,6 +130,36 @@ func DefaultDial(connectionTimeout time.Duration) func(network, addr string) (ne } } +// Option type for Dial +type Option func(*Config) error + +// SetOptions set amqp connection options +func (a *Config) SetOptions(opts ...Option) error { + for _, opt := range opts { + if err := opt(a); err != nil { + return err + } + } + + return nil +} + +// TLS is a wrapper for tls.Config to send as a Dial Option +func TLS(val *tls.Config) Option { + return func(t *Config) error { + t.TLSClientConfig = val + return nil + } +} + +// Auth is a wrapper for SASL to send as a Dial Option +func Auth(val []Authentication) Option { + return func(t *Config) error { + t.SASL = val + return nil + } +} + // Dial accepts a string in the AMQP URI format and returns a new Connection // over TCP using PlainAuth. Defaults to a server heartbeat interval of 10 // seconds and sets the handshake deadline to 30 seconds. After handshake, @@ -137,11 +167,13 @@ func DefaultDial(connectionTimeout time.Duration) func(network, addr string) (ne // // Dial uses the zero value of tls.Config when it encounters an amqps:// // scheme. It is equivalent to calling DialTLS(amqp, nil). -func Dial(url string) (*Connection, error) { - return DialConfig(url, Config{ +func Dial(url string, opts ...Option) (*Connection, error) { + config := Config{ Heartbeat: defaultHeartbeat, Locale: defaultLocale, - }) + } + config.SetOptions(opts...) + return DialConfig(url, config) } // DialTLS accepts a string in the AMQP URI format and returns a new Connection @@ -155,6 +187,25 @@ func DialTLS(url string, amqps *tls.Config) (*Connection, error) { TLSClientConfig: amqps, Locale: defaultLocale, }) + +} + +// DialTLSExternalAuth accepts a string in the AMQP URI format and returns a new +// Connection over TCP using EXTERNAL auth. Defaults to a server heartbeat +// interval of 10 seconds and sets the initial read deadline to 30 seconds. +// +// This mechanism is used, when RabbitMQ is configured for EXTERNAL auth with +// ssl_cert_login plugin for userless/passwordless logons +// +// DialTLS_CertAuth uses the provided tls.Config when encountering an amqps:// +// scheme. +func DialTLSExternalAuth(url string, amqps *tls.Config) (*Connection, error) { + + return DialConfig(url, Config{ + Heartbeat: defaultHeartbeat, + TLSClientConfig: amqps, + SASL: []Authentication{&ExternalAuth{}}, + }) } // DialConfig accepts a string in the AMQP URI format and a configuration for diff --git a/examples_test.go b/examples_test.go index 278a6dd1..4983816a 100644 --- a/examples_test.go +++ b/examples_test.go @@ -104,6 +104,36 @@ func ExampleDialTLS() { log.Printf("conn: %v, err: %v", conn, err) } +func ExampleDial_withTLSandExternalAuth() { + // This example assumes you enabled the rabbitmq-auth-mechanism-ssl plugin + // and your RabbitMQ server has TLS enabled + + // The username will be read from the DN or CN fields of the certificate + // you provide, you can see the more detailed information provided here + // https://github.com/rabbitmq/rabbitmq-auth-mechanism-ssl#username-extraction-from-certificate + + cfg := new(tls.Config) + + // see at the top + cfg.RootCAs = x509.NewCertPool() + + if ca, err := ioutil.ReadFile("testca/cacert.pem"); err == nil { + cfg.RootCAs.AppendCertsFromPEM(ca) + } + + // Move the client cert and key to a location specific to your application + // and load them here. + + if cert, err := tls.LoadX509KeyPair("client/cert.pem", "client/key.pem"); err == nil { + cfg.Certificates = append(cfg.Certificates, cert) + } + + // If you don't supply the Auth method as EXTERNAL, connection wouldn't fail and you will be logged in as the guest user. + conn, err := amqp.Dial("amqps://server-name-from-certificate/", amqp.TLS(cfg), amqp.Auth([]amqp.Authentication{&amqp.ExternalAuth{}})) + + log.Printf("conn: %v, err: %v", conn, err) +} + func ExampleChannel_Confirm_bridge() { // This example acts as a bridge, shoveling all messages sent from the source // exchange "log" to destination exchange "log".