Skip to content

Commit

Permalink
Add support for connection.update-secret (#114)
Browse files Browse the repository at this point in the history
* Add support for connection.update-secret

This method is in the AMQP 0.9.1 spec and it is supported by RabbitMQ.
This is useful when the secret used in the connection has an expiry, as
it allows the applications to renew the secret without the hassle of
closing-reconnecting consumers/producers.

Signed-off-by: Aitor Perez Cedres <acedres@vmware.com>

* Add documentation for Connection.UpdateSecret

Setting up an OAuth2 server to end-2-end test this function feels a bit
overkill. This function is used mainly in the context of OAuth2
authentication, where tokens expire. From this client perspective, all
it has to ensure is that a specific frame is put out in the wire to
RabbitMQ server. Then we trust that RabbitMQ will hold its part of the
contract and do the right thing.

Signed-off-by: Aitor Perez Cedres <acedres@vmware.com>

Signed-off-by: Aitor Perez Cedres <acedres@vmware.com>
  • Loading branch information
Zerpet committed Sep 6, 2022
1 parent 6839a62 commit ac70118
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 0 deletions.
16 changes: 16 additions & 0 deletions connection.go
Expand Up @@ -266,6 +266,22 @@ func Open(conn io.ReadWriteCloser, config Config) (*Connection, error) {
return c, c.open(config)
}

/*
UpdateSecret updates the secret used to authenticate this connection. It is used when
secrets have an expiration date and need to be renewed, like OAuth 2 tokens.
It returns an error if the operation is not successful, or if the connection is closed.
*/
func (c *Connection) UpdateSecret(newSecret, reason string) error {
if c.IsClosed() {
return ErrClosed
}
return c.call(&connectionUpdateSecret{
NewSecret: newSecret,
Reason: reason,
}, &connectionUpdateSecretOk{})
}

/*
LocalAddr returns the local TCP peer address, or ":0" (the zero value of net.TCPAddr)
as a fallback default value if the underlying transport does not support LocalAddr().
Expand Down
69 changes: 69 additions & 0 deletions examples_test.go
Expand Up @@ -9,6 +9,7 @@ import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"io/ioutil"
"log"
"net"
Expand Down Expand Up @@ -434,3 +435,71 @@ func ExampleTable_SetClientConnectionName() {
}
defer conn.Close()
}

func ExampleConnection_UpdateSecret() {
// In order to authenticate into RabbitMQ, the application must acquire a JWT token.
// This may be different, depending on the library used to communicate with the OAuth2
// server. This examples assumes that it's possible to obtain tokens using username+password.
//
// The authentication is successful if RabbitMQ can validate the JWT with the OAuth2 server.
// The permissions are determined from the scopes. Check the OAuth2 plugin readme for more details:
// https://github.com/rabbitmq/rabbitmq-server/tree/main/deps/rabbitmq_auth_backend_oauth2#scope-to-permission-translation
//
// Once the app has a JWT token, this can be used as credentials in the URI used in Connection.Dial()
//
// The app should have a long-running task that checks the validity of the JWT token, and renew it before
// the refresher time expires. Once a new JWT token is obtained, it shall be used in Connection.UpdateSecret().

token, _ := getJWToken("username", "password")

uri := fmt.Sprintf("amqp://%s:%s@localhost:5672", "client_id", token)
c, _ := amqp.Dial(uri)

defer c.Close()

// It also calls Connection.UpdateSecret()
tokenRefresherTask := func(conn *amqp.Connection, token string) {
// if token is expired
// then
renewedToken, _ := refreshJWToken(token)
_ = conn.UpdateSecret(renewedToken, "Token refreshed!")
}

go tokenRefresherTask(c, "my-JWT-token")

ch, _ := c.Channel()
defer ch.Close()

_, _ = ch.QueueDeclare(
"test-amqp",
false,
false,
false,
false,
amqp.Table{},
)
_ = ch.PublishWithContext(
context.Background(),
"",
"test-amqp",
false,
false,
amqp.Publishing{
Headers: amqp.Table{},
ContentType: "text/plain",
ContentEncoding: "",
DeliveryMode: amqp.Persistent,
Body: []byte("message"),
},
)
}

func getJWToken(username, password string) (string, error) {
// do OAuth2 things
return "a-token", nil
}

func refreshJWToken(token string) (string, error) {
// do OAuth2 things to refresh tokens
return "so fresh!", nil
}

0 comments on commit ac70118

Please sign in to comment.