GOSS uses Apache Shiro for authentication and authorization, integrated with ActiveMQ via the ShiroPlugin. All broker connections (OpenWire, STOMP) require valid credentials and are subject to permission checks.
Users are defined in a property file with one user per line:
File: pnnl.goss.core.runner/conf/pnnl.goss.core.security.propertyfile.cfg
Format:
username=password,permission1,permission2,...
Example:
system=manager,queue:*,topic:*,temp-queue:*
craig=craig,queue:*,topic:*,temp-queue:*
july=july,queue:*,topic:*,temp-queue:*Each line defines:
- username - The login name (left of
=) - password - The first value after
=(plaintext) - permissions - Comma-separated permission strings
Add a line to the property file and restart GOSS:
alice=secretpass,queue:*,topic:*,temp-queue:*For a read-only user that can only subscribe to topics:
reader=readerpass,topic:*The system user is the administrative account used internally by GOSS for broker management and token handling. It must always be present with at least queue:*,topic:*,temp-queue:* permissions.
Permissions use a colon-separated hierarchical format:
type:destination[:action]
| Part | Description | Examples |
|---|---|---|
| type | Destination type | queue, topic, temp-queue |
| destination | Destination name or wildcard | *, goss.gridappsd.process.request, ActiveMQ.Advisory.* |
| action | Optional action | read, write, create, subscribe |
The * wildcard matches any value at that level:
| Permission | Grants access to |
|---|---|
queue:* |
All queues, all actions |
topic:* |
All topics, all actions |
temp-queue:* |
All temporary queues |
topic:goss.gridappsd.* |
All topics under goss.gridappsd |
queue:request:write |
Write-only access to the request queue |
Full access (admin/system users):
queue:*,topic:*,temp-queue:*
GridAPPS-D application (typical simulation app):
topic:goss.gridappsd.simulation.*,queue:goss.gridappsd.process.request,temp-queue:*
Read-only subscriber:
topic:*
GOSS uses GossWildcardPermissionResolver to route permission checks:
- Permissions starting with
topic:,queue:, ortemp-queue:are handled by ActiveMQ'sActiveMQWildcardPermissionand enforced at the broker level - All other permissions use Shiro's standard
WildcardPermissionfor application-level access control
When a client attempts to send to or subscribe on a destination, ActiveMQ's ShiroPlugin checks the client's permissions against the requested destination. If the permission check fails, the operation is rejected.
JWT (JSON Web Token) authentication allows clients to authenticate once with credentials and then reconnect using a token, avoiding repeated credential transmission.
Client GOSS Server
| |
| 1. CONNECT (username/password) |
|-------------------------------------------->|
| |
| 2. CONNECTED |
|<--------------------------------------------|
| |
| 3. SUBSCRIBE /queue/temp.reply.xyz |
|-------------------------------------------->|
| |
| 4. SEND to /topic/pnnl.goss.token.topic |
| body: base64(username:password) |
| reply-to: /queue/temp.reply.xyz |
|-------------------------------------------->|
| |
| 5. MESSAGE on /queue/temp.reply.xyz |
| body: <JWT token> |
|<--------------------------------------------|
| |
| 6. DISCONNECT |
|-------------------------------------------->|
| |
| 7. CONNECT (token as username, empty pass) |
|-------------------------------------------->|
| |
| 8. CONNECTED (authenticated via token) |
|<--------------------------------------------|
- Connect with credentials - Standard STOMP/OpenWire connection with username and password
- Subscribe to a reply queue - Create a temporary queue to receive the token response
- Request a token - Send
base64(username:password)to the token topic (/topic/pnnl.goss.token.topic) with areply-toheader pointing to your reply queue - Receive the token - The server validates credentials and responds with a JWT token (or
"authentication failed") - Reconnect with token - Disconnect and reconnect using the JWT token as the username with an empty password
Tokens are signed with HMAC-SHA256 (HS256) and contain:
| Claim | Description |
|---|---|
sub |
Username |
roles |
List of permission strings |
iat |
Issued-at timestamp |
exp |
Expiration (5 days from issuance) |
import base64
import stomp
import time
HOST = "localhost"
PORT = 61618
USERNAME = "system"
PASSWORD = "manager"
TOKEN_TOPIC = "/topic/pnnl.goss.token.topic"
# Step 1: Connect with credentials
conn = stomp.Connection12([(HOST, PORT)])
conn.connect(USERNAME, PASSWORD, wait=True)
# Step 2: Set up a listener for the token response
class TokenListener(stomp.ConnectionListener):
def __init__(self):
self.token = None
def on_message(self, frame):
self.token = frame.body
listener = TokenListener()
conn.set_listener("token", listener)
# Step 3: Subscribe to reply queue and request token
reply_queue = "/queue/temp.token.reply"
conn.subscribe(destination=reply_queue, id="1", ack="auto")
time.sleep(0.3)
payload = base64.b64encode(f"{USERNAME}:{PASSWORD}".encode()).decode()
conn.send(
destination=TOKEN_TOPIC,
body=payload,
headers={"reply-to": reply_queue},
)
# Step 4: Wait for token
time.sleep(2)
token = listener.token
print(f"Got token: {token[:50]}...")
conn.disconnect()
# Step 5: Reconnect with token (token as username, empty password)
conn2 = stomp.Connection12([(HOST, PORT)])
conn2.connect(token, "", wait=True)
print("Connected with token!")
conn2.disconnect()// Using GOSS ClientFactory with token support
ClientFactory factory = // ... get from OSGi or direct instantiation
Credentials creds = new UsernamePasswordCredentials(username, password);
Client client = factory.create(PROTOCOL.OPENWIRE, creds, true); // useToken=trueFor OSGi deployments, JWT settings are configured via:
goss.system.manager=system
goss.system.manager.password=managerThe JWT signing key is derived automatically from the system manager credentials. For custom key management, implement the SecurityConfig interface.
GOSS registers multiple Shiro realms with the security manager:
| Realm | Purpose | Authentication method |
|---|---|---|
| PropertyRealm | Property-file users | Username + password |
| TokenRealm | JWT token users | Token as username, empty password |
| SystemRealm (OSGi) | System admin account | Hardcoded system credentials |
Token-based authentication is detected by: username length > 250 characters and empty password. This heuristic works because JWT tokens are always longer than any reasonable username.
| Class | Package | Role |
|---|---|---|
SecurityConfigImpl |
pnnl.goss.core.security.impl |
JWT token creation and validation (HS256) |
JWTAuthenticationToken |
pnnl.goss.core.security |
JWT claims data structure |
GossWildcardPermissionResolver |
pnnl.goss.core.security.impl |
Routes ActiveMQ vs. application permissions |
PropertyBasedRealm |
pnnl.goss.core.security.propertyfile |
Property-file user authentication (OSGi) |
GossSecurityManager |
pnnl.goss.core.security |
Central security coordination |
RoleManager |
pnnl.goss.core.security |
Role-to-permission mapping |
The SimpleRunner (GossSimpleRunner) wires security directly without OSGi:
- Reads users from the property file at startup
- Creates
PropertyRealmandTokenRealmas inner classes - Handles token requests via a direct JMS listener on the token topic
- Attaches
ShiroPluginto the embedded ActiveMQ broker
The OSGi Runner uses Declarative Services to wire security components:
PropertyBasedRealm,SystemRealm,UnauthTokenBasedRealmare OSGi componentsUserRepositoryImplhandles token requests via GOSS Client abstractionsRoleManagerImplprovides role-to-permission mapping from configuration files
Run the STOMP token authentication integration tests:
make itestThis builds GOSS, starts it in the background, runs the Python test suite against the STOMP port, and stops GOSS when done. The tests verify:
- Credential-based STOMP connection
- JWT token request and response
- Token-based reconnection
- Publish/subscribe with token authentication
- Invalid credential rejection
- Empty token rejection
The test suite is in pnnl.goss.core.itests/ and uses pixi for Python dependency management.