Skip to content
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

Add security mode and policy support #12

Merged
merged 10 commits into from
Nov 14, 2023
149 changes: 95 additions & 54 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# benthos-umh

[![License: Apache 2.0](https://img.shields.io/badge/License-Apache2.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)
[![GitHub Actions](https://github.com/united-manufacturing-hub/benthos-umh/workflows/main/badge.svg)](https://github.com/united-manufacturing-hub/benthos-umh/actions)

Expand All @@ -24,6 +25,7 @@ We encourage you to try out `benthos-umh` and explore the broader [United Manufa
To use benthos-umh in standalone mode with Docker, follow the instructions in the main article provided.

1. Create a new file called benthos.yaml with the provided content

```yaml
---
input:
Expand All @@ -46,9 +48,10 @@ To use benthos-umh in standalone mode with Docker, follow the instructions in th
topic: 'ia/raw/opcuasimulator/${! meta("opcua_path") }'
client_id: 'benthos-umh'
```

2. Execute the docker run command to start a new benthos-umh container
`docker run --rm --network="host" -v '<absolute path to your file>/benthos.yaml:/benthos.yaml' ghcr.io/united-manufacturing-hub/benthos-umh:latest`

### With the United Manufacturing Hub (Kubernetes & Kafka)

To deploy benthos-umh with the United Manufacturing Hub and its OPC-UA simulator, use the provided Kubernetes manifests in UMHLens/OpenLens.
Expand All @@ -73,7 +76,7 @@ data:
"timestamp_unix": timestamp_unix()
}
output:
umh_output:
umh_output:
topic: 'ia.raw.${! meta("opcua_path") }'
---
apiVersion: apps/v1
Expand Down Expand Up @@ -120,73 +123,93 @@ spec:
name: benthos-1-config
```

### Pull Every Second vs Subscribe
### Authentication and Security

#### Pull Method
Advantages:
- Provides real-time data visibility, e.g., in MQTT Explorer.
- Clearly differentiates between 'no data received' and 'value did not change' scenarios, which can be crucial for documentation and proving the OPC-UA client's activity.
In benthos-umh, security and authentication are designed to be as robust as possible while maintaining flexibility. The software automates the process of selecting the highest level of security offered by an OPC-UA server for the selected Authentication Method, but the user can specify their own Security Policy / Security Mode if they want (see further below at Configuration options)

Disadvantages:
- Results in higher data throughput as it pulls all nodes every second, regardless of changes.
#### Supported Authentication Methods

#### Subscribe Method
- **Anonymous**: No extra information is needed. The connection uses the highest security level available for anonymous connections.
- **Username and Password**: Specify the username and password in the configuration. The client opts for the highest security level that supports these credentials.
- **Certificate (Future Release)**: Certificate-based authentication is planned for future releases.

Advantages:
- Data is sent only when there's a change in value, reducing unnecessary data transfer.
Disadvantages:
- Less visibility into real-time data status, and it's harder to differentiate between no data and unchanged values.
### Configuration Options

The following options can be specified in the `benthos.yaml` configuration file:

#### Configuration
```yaml
input:
opcua:
endpoint: 'opc.tcp://localhost:46010'
nodeIDs: ['ns=2;s=IoTSensors']
username: 'your-username' # optional (default: unset)
password: 'your-password' # optional (default: unset)
insecure: false | true # optional (default: false)
securityMode: None | Sign | SignAndEncrypt # optional (default: unset)
securityPolicy: None | Basic256Sha256 | Aes256Sha256RsaPss | Aes128Sha256RsaOaep # optional (default: unset)
subscribeEnabled: false | true # optional (default: false)
```

#### Endpoint

If not specified otherwise, benthos will use the Pull Method as described above. To enable subscription mode, set subscribeEnabled: true in the configuration:
You can specify the endpoint in the configuration file. Node endpoints are automatically discovered and selected based on the authentication method.

```yaml
input:
opcua:
endpoint: 'opc.tcp://localhost:46010'
nodeIDs: ['ns=2;s=IoTSensors']
subscribeEnabled: true
```

### Authentication and Security
#### Node IDs

In benthos-umh, security and authentication are designed to be as robust as possible while maintaining flexibility. The software automates the process of selecting the highest level of security offered by an OPC-UA server for the selected Authentication Method.
You can specify the node IDs in the configuration file (currently only namespaced node IDs are supported):

#### How It Works
```yaml
input:
opcua:
endpoint: 'opc.tcp://localhost:46010'
nodeIDs: ['ns=2;s=IoTSensors']
```

1. **Discover Endpoints**: Initially, benthos-umh discovers all available endpoints from the OPC-UA server.
2. **Filter by Authentication**: Based on the provided authentication method, it filters the list of endpoints. It currently supports Anonymous and Username/Password methods. Certificate-based authentication is on the roadmap.
3. **Select Endpoint**: The software then chooses the endpoint with the highest security level that matches the chosen authentication method.
4. **Client Initialization**: Various client options are initialized, such as security policies, based on the selected endpoint.
5. **Generate Certificates**: For secure communication, certificates are dynamically generated. However, this step is only essential for methods requiring it.
6. **Final Connection**: Finally, it initiates a connection to the OPC-UA server using the chosen endpoint and authentication method.
#### Username and Password

#### Supported Authentication Methods
If you want to use username and password authentication, you can specify them in the configuration file:

- **Anonymous**: No extra information is needed. The connection uses the highest security level available for anonymous connections.

- **Username and Password**: Specify the username and password in the configuration. The client opts for the highest security level that supports these credentials.

- **Certificate (Future Release)**: Certificate-based authentication is planned for future releases.
```yaml
input:
opcua:
endpoint: 'opc.tcp://localhost:46010'
nodeIDs: ['ns=2;s=IoTSensors']
username: 'your-username'
password: 'your-password'
```

#### Example: Configuration File
#### Security Mode and Security Policy

Here is how you could specify authentication in `benthos.yaml`:
Security Mode: This defines the level of security applied to the messages. The options are:
- None: No security is applied; messages are neither signed nor encrypted.
- Sign: Messages are signed for integrity and authenticity but not encrypted.
- SignAndEncrypt: Provides the highest security level where messages are both signed and encrypted.

Security Policy: Specifies the set of cryptographic algorithms used for securing messages. This includes algorithms for encryption, decryption, and signing of messages. Some common policies include Basic256Sha256, Aes256Sha256RsaPss, and Aes128Sha256RsaOaep.

While the security mode and policy are automatically selected based on the endpoint and authentication method, you have the option to override this by specifying them in the configuration file:

```yaml
input:
opcua:
endpoint: 'opc.tcp://localhost:46010'
nodeIDs: ['ns=2;s=IoTSensors']
username: 'your-username' # optional
password: 'your-password' # optional
securityMode: SignAndEncrypt
securityPolicy: Basic256Sha256
```

#### Troubleshooting
#### Insecure Mode

Setting this to true will overwrite any configured securityMode and securityPolicy!

If the most secure endpoint selected by benthos-umh is not working or the server's security implementation is lacking, you can bypass encryption by setting `insecure: true``.
If the most secure endpoint selected by benthos-umh is not working or the server's security implementation is lacking, you can bypass encryption by setting `insecure: true`. This will use the Security Mode "None".

```yaml
input:
Expand All @@ -196,47 +219,66 @@ input:
insecure: true
```

#### Pull and Subscribe Methods

Benthus-umh supports two modes of operation: pull and subscribe. In pull mode, it pulls all nodes every second, regardless of changes. In subscribe mode, it only sends data when there's a change in value, reducing unnecessary data transfer.

| Method | Advantages | Disadvantages |
| --- | --- | --- |
| Pull | - Provides real-time data visibility, e.g., in MQTT Explorer. <br> - Clearly differentiates between 'no data received' and 'value did not change' scenarios, which can be crucial for documentation and proving the OPC-UA client's activity. | - Results in higher data throughput as it pulls all nodes every second, regardless of changes. |
| Subscribe | - Data is sent only when there's a change in value, reducing unnecessary data transfer. | - Less visibility into real-time data status, and it's harder to differentiate between no data and unchanged values. |

```yaml
input:
opcua:
endpoint: 'opc.tcp://localhost:46010'
nodeIDs: ['ns=2;s=IoTSensors']
subscribeEnabled: true
```

## Testing

We execute automated tests and verify that benthos-umh works:

- (WAGO PFC100, 750-8101) Connect Anonymously
- (WAGO PFC100, 750-8101) Connect Username / Password
- (WAGO PFC100, 750-8101) Connect and get one float number
- (WAGO PFC100, 750-8101) Connect and subscribe to two numbers (and verify that only data gets sent if it has changed)

These tests are executed with a local github runner called "hercules", which is connected to a isolated testing network.

## Development

### Quickstart

Follow the steps below to set up your development environment and run tests:

```
$ git clone https://github.com/united-manufacturing-hub/benthos-umh.git
$ cd serverless-stack
$ nvm install
$ npm install
$ sudo apt-get install zip
$ echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list
$ sudo apt update
$ sudo apt install goreleaser
$ make
$ npm test
git clone https://github.com/united-manufacturing-hub/benthos-umh.git
cd serverless-stack
nvm install
npm install
sudo apt-get install zip
echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list
sudo apt update
sudo apt install goreleaser
make
npm test
```

### Additional Checks and Commands

#### Gitpod and Tailscale

By default when opening the repo in Gitpod, everything that you need should start automatically. If you want to connect to our local PLCs in our office, you can use tailscale, which you will be prompted to install.
See also: https://www.gitpod.io/docs/integrations/tailscale
See also: <https://www.gitpod.io/docs/integrations/tailscale>

#### For Go Code:
#### For Go Code

1. **Linting**: Run `make lint` to check for linting errors. If any are found, you can automatically fix them by running `make format`.

2. **Unit Tests**: Run `make test` to execute all Go unit tests.

#### For Other Code Types (Including Config Files):
#### For Other Code Types (Including Config Files)

1. **Benthos Tests**: Use `npm run test` to run all Benthos tests for configuration files. Note: We currently do not have these tests. [Learn more](https://www.benthos.dev/docs/configuration/unit_testing/).

Expand All @@ -251,4 +293,3 @@ All source code is distributed under the APACHE LICENSE, VERSION 2.0. See LICENS
Feel free to provide us feedback on our [Discord channel](https://discord.gg/F9mqkZnm9d).

For more information about the United Manufacturing Hub, visit [UMH Systems GmbH](https://www.umh.app). If you haven't worked with the United Manufacturing Hub before, [give it a try](https://umh.docs.umh.app/docs/getstarted/installation/)! Setting it up takes only a matter of minutes.

58 changes: 45 additions & 13 deletions plugin/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,65 @@ import (
"github.com/gopcua/opcua/ua"
)

func (g *OPCUAInput) getReasonableEndpoint(endpoints []*ua.EndpointDescription, selectedAuthentication ua.UserTokenType, disableEncryption bool) *ua.EndpointDescription {
// getReasonableEndpoint selects an appropriate OPC UA endpoint based on specified criteria.
// It filters the endpoints based on the authentication method, security mode, and security policy.
// If no suitable endpoint is found, it returns nil.
// This can potentially be replaced by SelectEndpoint function in goopcua package
func (g *OPCUAInput) getReasonableEndpoint(
endpoints []*ua.EndpointDescription,
selectedAuthentication ua.UserTokenType,
disableEncryption bool,
securityMode string,
securityPolicy string,
) *ua.EndpointDescription {

// Return nil immediately if no endpoints are provided.
if len(endpoints) == 0 {
return nil
}

// Sort endpoints in descending order of security level.
sort.Sort(sort.Reverse(bySecurityLevel(endpoints)))

for _, p := range endpoints {
// Take the first endpoint that supports our selectedAuthentication
for _, userIdentity := range p.UserIdentityTokens {
// Iterate over each endpoint to find a matching one.
for _, endpoint := range endpoints {

// Check each user identity token in the endpoint.
for _, userIdentity := range endpoint.UserIdentityTokens {

// Match the endpoint with the selected authentication type.
if selectedAuthentication == userIdentity.TokenType {

// Additionally check whether encryption is disabled
if disableEncryption && p.SecurityMode == ua.MessageSecurityModeFromString("None") {
return p
} else if !disableEncryption { // if encrpytion is not disabled, then take everything
return p
}
// otherwise, continue searching
// Check for encryption requirements.
if disableEncryption && endpoint.SecurityMode == ua.MessageSecurityModeFromString("None") {
// Return if encryption is disabled and the endpoint has no security.
return endpoint
} else if !disableEncryption {
// Handle the case where encryption is not disabled.

// Check for a specific security mode if provided.
if securityMode != "" && endpoint.SecurityMode == ua.MessageSecurityModeFromString(securityMode) {

// Check for a specific security policy if provided.
if securityPolicy != "" && endpoint.SecurityPolicyURI == "http://opcfoundation.org/UA/SecurityPolicy#"+securityPolicy {
return endpoint
} else if securityPolicy == "" {
// If no specific security policy is needed, return the endpoint.
return endpoint
}
// Continue searching if the security policy doesn't match.
} else if securityMode == "" {
// If no specific security policy is needed, return the endpoint.
return endpoint
}
}
// Continue searching if other conditions are not met.
}
}

}
return nil

// Return nil if no suitable endpoint is found.
return nil
}

// Copy paste from opcua library
Expand Down
Loading