Skip to content

Azure Recipe Pack MySQL requires TLS, but the mySqlDatabases contract exposes no way to connect securely #301

Description

@nellshamrell

Steps to reproduce

Model an application that connects to Radius.Data/mySqlDatabases using the type's documented outputs, then deploy it to an AKS environment using the Azure Recipe Pack (recipe-packs/azure/aks-recipepack.bicep).

resource mysqlDb 'Radius.Data/mySqlDatabases@2025-08-01-preview' = {
  name: 'mysql'
  properties: {
    environment: environment
    application: todoApp.id
    database: 'todos'
    version: '8.0'
    username: 'myadmin'
    password: mysqlPassword
  }
}

resource todoContainer 'Radius.Compute/containers@2025-08-01-preview' = {
  name: 'todo-list-app'
  properties: {
    environment: environment
    application: todoApp.id
    containers: {
      todo: {
        image: todoImage.properties.imageReference
        ports: {
          web: {
            containerPort: 3000
          }
        }
        env: {
          MYSQL_HOST: {
            value: mysqlDb.properties.host
          }
          MYSQL_USER: {
            value: 'myadmin'
          }
          MYSQL_PASSWORD: {
            value: mysqlPassword
          }
          MYSQL_DB: {
            value: 'todos'
          }
        }
      }
    }
  }
}

The application is the standard todo-list-app Node.js sample, whose MySQL client is a plain mysql2 pool built from the decomposed host/user/password/database values:

pool = mysql.createPool({
    connectionLimit: 5,
    host,
    user,
    password,
    database,
    charset: 'utf8mb4',
});

rad deploy succeeds and every resource reports success.

Observed behavior

The deployment is reported as successful, but the application container never becomes healthy. It resolves the server, completes the TCP connection, and then fails the MySQL handshake:

Waiting for mysql-dc4801e47880faf3.mysql.database.azure.com:3306.
Connected!
Error: Connections using insecure transport are prohibited while --require_secure_transport=ON.
    at Packet.asError (/app/node_modules/mysql2/lib/packets/packet.js:728:17)
  code: undefined,
  errno: 3159,
  sqlState: 'HY000',
  sqlMessage: 'Connections using insecure transport are prohibited while --require_secure_transport=ON.',

The pod crash-loops indefinitely. Radius reports the deployment as fully successful throughout, so the failure is only visible by inspecting pod logs.

The root cause is a gap between what the Azure recipe provisions and what the resource type's contract exposes:

  • The Azure Recipe Pack provisions MySQL through mcr.microsoft.com/bicep/avm/res/db-for-my-sql/flexible-server:0.10.3 and does not pass any serverConfigurations, so the server keeps the Azure default of require_secure_transport = ON.
  • The mySqlDatabases schema exposes only host and port as read-only outputs. There is no TLS mode, no sslMode/requireSsl indicator, and no CA certificate reference.
  • The documented connection projection is likewise limited to CONNECTION_<NAME>_DATABASE, CONNECTION_<NAME>_HOST, and CONNECTION_<NAME>_PORT.

So there is no property an application definition can read to discover that TLS is mandatory, and no input it can set to influence it. Wiring every documented output correctly still produces an application that cannot connect.

This also breaks portability across recipes for the same resource type. The Kubernetes recipe runs a stock mysql:${version} image with no TLS enforcement, so an identical application definition works there and fails on Azure. The portable resource type is the same in both cases, which makes this a behavioral difference users cannot see in the schema.

Desired behavior

An application definition that consumes the documented mySqlDatabases outputs should be able to connect to the provisioned database on every platform that offers a recipe for the type.

Any of the following would close the gap:

  1. Expose the transport contract on the type as read-only outputs (for example a TLS/SSL mode and, where relevant, a CA certificate reference) so an application definition can configure its client and so the value is visible in the connection projection.
  2. Have the Azure recipe converge on the same transport behavior as the other recipes for this type, so the portable type behaves consistently.
  3. Make the transport requirement a schema-level input so the intent is explicit and validated at author time rather than discovered from a crash loop.

Option 1 seems the most aligned with the portability goal, since it keeps the Azure security default intact while making the requirement discoverable.

Separately, it seems worth considering whether a resource whose only consumer cannot connect should be reported as a fully successful deployment.

The same question likely applies to postgreSqlDatabases, whose Azure Flexible Server default is also TLS-required and whose schema likewise exposes only host and port.

Workaround

Modify the application source to opt into TLS explicitly, for example by adding an ssl option to the client:

pool = mysql.createPool({
    connectionLimit: 5,
    host,
    user,
    password,
    database,
    charset: 'utf8mb4',
    ssl: { minVersion: 'TLSv1.2' },
});

This requires changing application code to suit one specific recipe's provisioning behavior, which works against the portability the resource type is meant to provide. It is also only available when the application source can be modified, so it does not help with an unmodified third-party image.

rad Version

RELEASE   VERSION                        BICEP     COMMIT
edge      v0.60.0-rc1-12-g02d5e0a-dirty  0.42.1    02d5e0aabb9ee3c238409010f27231dda95e986d

Operating system

Windows 11, x64 (client). Deploy target: AKS v1.35.6, westus3, 2 × Standard_D4s_v6.

Additional context

Metadata

Metadata

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions