From 795767a3491c56f438d7025846efbce68e102cd1 Mon Sep 17 00:00:00 2001 From: Michael Uti Date: Fri, 12 Apr 2024 13:41:56 +0100 Subject: [PATCH] ci: use cross docker image that supports openssl for musl arch --- .github/actions/build_binaries/action.yml | 2 +- .github/workflows/release-draft-binaries.yml | 328 +++++++++--------- Cargo.lock | 92 ++++- Cargo.toml | 8 - Cross.toml | 16 + ...bute-based-authentication-control-plane.rs | 3 +- .../examples/01-inlet-outlet.rs | 10 +- .../examples/02-outlet.rs | 4 +- .../examples/03-outlet.rs | 4 +- .../examples/04-outlet.rs | 4 +- .../ockam_api/src/cli_state/tcp_portals.rs | 0 .../ockam_api/src/kafka/integration_test.rs | 8 +- .../ockam_api/src/kafka/outlet_controller.rs | 4 +- .../ockam_api/src/nodes/models/portal.rs | 21 +- .../src/nodes/service/kafka_services.rs | 13 +- .../ockam_api/src/nodes/service/portals.rs | 29 +- .../ockam/ockam_api/src/test_utils/mod.rs | 11 +- .../rust/ockam/ockam_api/tests/latency.rs | 4 +- .../rust/ockam/ockam_api/tests/portals.rs | 23 +- .../tests/trace_context_propagation.rs | 4 +- .../src/shared_service/tcp_outlet/create.rs | 5 +- .../src/shared_service/tcp_outlet/state.rs | 5 +- .../src/run/parser/resource/tcp_outlets.rs | 8 +- .../ockam_command/src/tcp/outlet/create.rs | 29 +- .../tests/bats/local/portals.bats | 12 + .../rust/ockam/ockam_transport_tcp/Cargo.toml | 5 + .../src/portal/inlet_listener.rs | 6 +- .../ockam_transport_tcp/src/portal/options.rs | 8 + .../src/portal/outlet_listener.rs | 16 +- .../src/portal/portal_receiver.rs | 13 +- .../src/portal/portal_worker.rs | 124 ++++--- .../src/transport/common.rs | 117 ++++++- .../src/transport/connection.rs | 10 +- .../src/transport/hostname_port.rs | 134 +++++++ .../ockam_transport_tcp/src/transport/mod.rs | 2 + .../src/transport/portals.rs | 43 ++- .../ockam_transport_tcp/src/workers/sender.rs | 42 +-- .../ockam/ockam_transport_tcp/tests/portal.rs | 12 +- tools/cross/Cross.Dockerfile.armv7 | 10 +- 39 files changed, 821 insertions(+), 368 deletions(-) create mode 100644 Cross.toml create mode 100644 implementations/rust/ockam/ockam_api/src/cli_state/tcp_portals.rs create mode 100644 implementations/rust/ockam/ockam_transport_tcp/src/transport/hostname_port.rs diff --git a/.github/actions/build_binaries/action.yml b/.github/actions/build_binaries/action.yml index d2610f21d9f..ac0d529e5d7 100644 --- a/.github/actions/build_binaries/action.yml +++ b/.github/actions/build_binaries/action.yml @@ -36,7 +36,7 @@ runs: set -x use_cross_build=${{ inputs.use_cross_build }} if [[ $use_cross_build == true ]]; then - cargo install --version 0.2.4 cross + cargo install --version 0.2.5 cross else sudo apt-get update sudo apt-get install -y --no-install-recommends xz-utils liblz4-tool musl-tools libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev patchelf diff --git a/.github/workflows/release-draft-binaries.yml b/.github/workflows/release-draft-binaries.yml index 34f3fc95b7b..7bdf7cdf8d3 100644 --- a/.github/workflows/release-draft-binaries.yml +++ b/.github/workflows/release-draft-binaries.yml @@ -184,167 +184,167 @@ jobs: matrix: build: [linux_arm64, linux_86, linux_armv7, macos_silicon, macos_86] include: - - build: linux_arm64 - os: ubuntu-22.04 - toolchain: stable - target: aarch64-unknown-linux-musl - build_app: false - use-cross-build: true - - build: linux_armv7 - os: ubuntu-22.04 - toolchain: stable - target: armv7-unknown-linux-musleabihf - use-cross-build: true - build_app: false - - build: linux_86 - os: ubuntu-22.04 - toolchain: stable - target: x86_64-unknown-linux-musl - use-cross-build: true - build_app: false - - build: linux_86_gnu - os: ubuntu-22.04 - toolchain: stable - target: x86_64-unknown-linux-gnu - use-cross-build: false - build_app: true - build_command: false - - build: macos_silicon - os: macos-14 - toolchain: stable - target: aarch64-apple-darwin - use-cross-build: false - build_app: true - - build: macos_86 - os: macos-14 - toolchain: stable - target: x86_64-apple-darwin - use-cross-build: false - build_app: true + - build: linux_arm64 + os: ubuntu-22.04 + toolchain: stable + target: aarch64-unknown-linux-musl + build_app: false + use-cross-build: true + - build: linux_armv7 + os: ubuntu-22.04 + toolchain: stable + target: armv7-unknown-linux-musleabihf + use-cross-build: true + build_app: false + - build: linux_86 + os: ubuntu-22.04 + toolchain: stable + target: x86_64-unknown-linux-musl + use-cross-build: true + build_app: false + - build: linux_86_gnu + os: ubuntu-22.04 + toolchain: stable + target: x86_64-unknown-linux-gnu + use-cross-build: false + build_app: true + build_command: false + - build: macos_silicon + os: macos-14 + toolchain: stable + target: aarch64-apple-darwin + use-cross-build: false + build_app: true + - build: macos_86 + os: macos-14 + toolchain: stable + target: x86_64-apple-darwin + use-cross-build: false + build_app: true runs-on: ${{ matrix.os }} steps: - - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 - with: - ref: ${{ github.event.inputs.release_branch }} - - - name: Echo Link - run: echo "${{ needs.create_release.outputs.upload_url }}" - - - name: Apple Signing Initialization - if: ${{ matrix.os == 'macos-14' }} - shell: bash - env: - BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} - P12_PASSWORD: ${{ secrets.P12_PASSWORD }} - BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} - KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} - run: | - set -ex - # Switch to xcode 15 - sudo xcode-select --switch /Applications/Xcode_15.0.app/ - - # create variables - CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 - PP_PATH=$RUNNER_TEMP/build_pp.provisionprofile - KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db - - # import certificate and provisioning profile from secrets - echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH - echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH - - # create temporary keychain - security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH - security set-keychain-settings -lut 21600 $KEYCHAIN_PATH - security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH - - # import certificate to keychain - security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH - security list-keychain -d user -s $KEYCHAIN_PATH - - # apply provisioning profile - mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles - cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles - - # Add keychain path to env - echo "KEYCHAIN_PATH=${KEYCHAIN_PATH}" >> "$GITHUB_ENV" - - - uses: ./.github/actions/build_binaries - with: - use_cross_build: ${{ matrix.use-cross-build }} - toolchain: ${{ matrix.toolchain }} - target: ${{ matrix.target }} - platform_operating_system: ${{ matrix.os }} - build_app: ${{ matrix.build_app }} - - - name: Copy Artifacts - run: | - set -x - - cp target/${{ matrix.target }}/release/ockam_command ockam.${{ matrix.target }} - echo "ASSET_OCKAM_CLI=ockam.${{ matrix.target }}" >> $GITHUB_ENV - if [ -e "implementations/swift/build/Ockam.dmg" ]; then - cp "implementations/swift/build/Ockam.dmg" "ockam.app.${{ matrix.target }}.dmg" - echo "ASSET_OCKAM_APP_DMG=ockam.app.${{ matrix.target }}.dmg" >> $GITHUB_ENV - fi - ls $GITHUB_WORKSPACE - - - name: Install Cosign - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 - with: - cosign-release: 'v2.0.0' - - - name: Sign Binaries - env: - PRIVATE_KEY: '${{ secrets.COSIGN_PRIVATE_KEY }}' - COSIGN_PASSWORD: '${{ secrets.COSIGN_PRIVATE_KEY_PASSWORD }}' - run: | - cosign sign-blob --yes --key env://PRIVATE_KEY "${{ env.ASSET_OCKAM_CLI }}" > "${{ env.ASSET_OCKAM_CLI }}.sig" - if [ -n "${{ env.ASSET_OCKAM_APP_DMG }}" ]; then - cosign sign-blob --yes --key env://PRIVATE_KEY "${{ env.ASSET_OCKAM_APP_DMG }}" > "${{ env.ASSET_OCKAM_APP_DMG }}.sig" - fi - - - name: Upload CLI release archive - uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create_release.outputs.upload_url }} - asset_path: ${{ env.ASSET_OCKAM_CLI }} - asset_name: ${{ env.ASSET_OCKAM_CLI }} - asset_content_type: application/octet-stream - - - name: Upload CLI Signature - uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create_release.outputs.upload_url }} - asset_path: ${{ env.ASSET_OCKAM_CLI }}.sig - asset_name: ${{ env.ASSET_OCKAM_CLI }}.sig - asset_content_type: application/octet-stream - - - name: Upload MacOS App release - uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa - if: ${{ env.ASSET_OCKAM_APP_DMG }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create_release.outputs.upload_url }} - asset_path: ${{ env.ASSET_OCKAM_APP_DMG }} - asset_name: ${{ env.ASSET_OCKAM_APP_DMG }} - asset_content_type: application/octet-stream - - - name: Upload MacOS App release Signature - uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa - if: ${{ env.ASSET_OCKAM_APP_DMG }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - with: - upload_url: ${{ needs.create_release.outputs.upload_url }} - asset_path: ${{ env.ASSET_OCKAM_APP_DMG }}.sig - asset_name: ${{ env.ASSET_OCKAM_APP_DMG }}.sig - asset_content_type: application/octet-stream + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 + with: + ref: ${{ github.event.inputs.release_branch }} + + - name: Echo Link + run: echo "${{ needs.create_release.outputs.upload_url }}" + + - name: Apple Signing Initialization + if: ${{ matrix.os == 'macos-14' }} + shell: bash + env: + BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }} + P12_PASSWORD: ${{ secrets.P12_PASSWORD }} + BUILD_PROVISION_PROFILE_BASE64: ${{ secrets.BUILD_PROVISION_PROFILE_BASE64 }} + KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }} + run: | + set -ex + # Switch to xcode 15 + sudo xcode-select --switch /Applications/Xcode_15.0.app/ + + # create variables + CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12 + PP_PATH=$RUNNER_TEMP/build_pp.provisionprofile + KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db + + # import certificate and provisioning profile from secrets + echo -n "$BUILD_CERTIFICATE_BASE64" | base64 --decode -o $CERTIFICATE_PATH + echo -n "$BUILD_PROVISION_PROFILE_BASE64" | base64 --decode -o $PP_PATH + + # create temporary keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + security set-keychain-settings -lut 21600 $KEYCHAIN_PATH + security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH + + # import certificate to keychain + security import $CERTIFICATE_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH + security list-keychain -d user -s $KEYCHAIN_PATH + + # apply provisioning profile + mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles + cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles + + # Add keychain path to env + echo "KEYCHAIN_PATH=${KEYCHAIN_PATH}" >> "$GITHUB_ENV" + + - uses: ./.github/actions/build_binaries + with: + use_cross_build: ${{ matrix.use-cross-build }} + toolchain: ${{ matrix.toolchain }} + target: ${{ matrix.target }} + platform_operating_system: ${{ matrix.os }} + build_app: ${{ matrix.build_app }} + + - name: Copy Artifacts + run: | + set -x + + cp target/${{ matrix.target }}/release/ockam_command ockam.${{ matrix.target }} + echo "ASSET_OCKAM_CLI=ockam.${{ matrix.target }}" >> $GITHUB_ENV + if [ -e "implementations/swift/build/Ockam.dmg" ]; then + cp "implementations/swift/build/Ockam.dmg" "ockam.app.${{ matrix.target }}.dmg" + echo "ASSET_OCKAM_APP_DMG=ockam.app.${{ matrix.target }}.dmg" >> $GITHUB_ENV + fi + ls $GITHUB_WORKSPACE + + - name: Install Cosign + uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 + with: + cosign-release: 'v2.0.0' + + - name: Sign Binaries + env: + PRIVATE_KEY: '${{ secrets.COSIGN_PRIVATE_KEY }}' + COSIGN_PASSWORD: '${{ secrets.COSIGN_PRIVATE_KEY_PASSWORD }}' + run: | + cosign sign-blob --yes --key env://PRIVATE_KEY "${{ env.ASSET_OCKAM_CLI }}" > "${{ env.ASSET_OCKAM_CLI }}.sig" + if [ -n "${{ env.ASSET_OCKAM_APP_DMG }}" ]; then + cosign sign-blob --yes --key env://PRIVATE_KEY "${{ env.ASSET_OCKAM_APP_DMG }}" > "${{ env.ASSET_OCKAM_APP_DMG }}.sig" + fi + + - name: Upload CLI release archive + uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: ${{ env.ASSET_OCKAM_CLI }} + asset_name: ${{ env.ASSET_OCKAM_CLI }} + asset_content_type: application/octet-stream + + - name: Upload CLI Signature + uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: ${{ env.ASSET_OCKAM_CLI }}.sig + asset_name: ${{ env.ASSET_OCKAM_CLI }}.sig + asset_content_type: application/octet-stream + + - name: Upload MacOS App release + uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa + if: ${{ env.ASSET_OCKAM_APP_DMG }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: ${{ env.ASSET_OCKAM_APP_DMG }} + asset_name: ${{ env.ASSET_OCKAM_APP_DMG }} + asset_content_type: application/octet-stream + + - name: Upload MacOS App release Signature + uses: actions/upload-release-asset@ef2adfe8cb8ebfa540930c452c576b3819990faa + if: ${{ env.ASSET_OCKAM_APP_DMG }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ needs.create_release.outputs.upload_url }} + asset_path: ${{ env.ASSET_OCKAM_APP_DMG }}.sig + asset_name: ${{ env.ASSET_OCKAM_APP_DMG }}.sig + asset_content_type: application/octet-stream build_elixir_nifs: @@ -358,10 +358,10 @@ jobs: fail-fast: false matrix: job: - - { target: aarch64-unknown-linux-gnu , os: ubuntu-20.04 , use-cross: true } - - { target: x86_64-unknown-linux-gnu , os: ubuntu-20.04 } - - { target: aarch64-apple-darwin , os: macos-14 } - - { target: x86_64-apple-darwin , os: macos-14 } + - {target: aarch64-unknown-linux-gnu , os: ubuntu-20.04 , use-cross: true} + - {target: x86_64-unknown-linux-gnu , os: ubuntu-20.04} + - {target: aarch64-apple-darwin , os: macos-14} + - {target: x86_64-apple-darwin , os: macos-14} runs-on: ${{ matrix.job.os }} steps: @@ -378,7 +378,7 @@ jobs: - name: Install Cross if: matrix.job.use-cross == true - run: cargo install --version 0.2.4 cross + run: cargo install --version 0.2.5 cross - name: Build NIFs run: | diff --git a/Cargo.lock b/Cargo.lock index 789065eb704..79853bf1566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1433,7 +1433,7 @@ dependencies = [ "cocoa-foundation", "core-foundation", "core-graphics", - "foreign-types", + "foreign-types 0.5.0", "libc", "objc", ] @@ -1586,7 +1586,7 @@ dependencies = [ "bitflags 1.3.2", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -2563,6 +2563,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2570,7 +2579,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -2584,6 +2593,12 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -3988,6 +4003,24 @@ dependencies = [ "serde", ] +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nb" version = "0.1.3" @@ -4625,15 +4658,20 @@ version = "0.111.0" dependencies = [ "cfg-if", "hashbrown 0.14.3", + "minicbor", + "native-tls", "ockam_core", "ockam_macros", "ockam_node", "ockam_transport_core", "opentelemetry", "rand", + "regex", + "rustls-native-certs 0.7.0", "serde", "socket2 0.5.6", "tokio", + "tokio-native-tls", "tracing", ] @@ -4780,12 +4818,50 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.4.2", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" +[[package]] +name = "openssl-sys" +version = "0.9.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dda2b0f344e78efc2facf7d195d098df0dd72151b26ab98da807afc26c198dff" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "opentelemetry" version = "0.22.0" @@ -7201,6 +7277,16 @@ dependencies = [ "syn 2.0.58", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-retry" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index 85edce3e7af..20ab2beac7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,11 +34,3 @@ inherits = "release" opt-level = 2 [profile.dev.package.adler] opt-level = 1 - -# Allows us build with cross across multiple platforms. -[workspace.metadata.cross.build] -# additional commands to run prior to building the package -pre-build = [ - "dpkg --add-architecture $CROSS_DEB_ARCH", - "apt-get update && apt-get --assume-yes install libssl-dev:$CROSS_DEB_ARCH", -] diff --git a/Cross.toml b/Cross.toml new file mode 100644 index 00000000000..8d989915a7e --- /dev/null +++ b/Cross.toml @@ -0,0 +1,16 @@ +[target.armv7-unknown-linux-musleabihf] +image = "rustembedded/cross:armv7-unknown-linux-musleabihf-0.1.16" + +[target.x86_64-unknown-linux-musl] +image = "rustembedded/cross:x86_64-unknown-linux-musl-0.1.16" + +[target.aarch64-unknown-linux-musl] +image = "rustembedded/cross:aarch64-unknown-linux-musl-0.1.16" + +[build] +pre-build = [ # additional commands to run prior to building the package + "dpkg --add-architecture $CROSS_DEB_ARCH", + # Cross runs the pre-build command for all target and libssl-dev:$CROSS_DEB_ARCH + # install fails for musl archs, so we fall back to using just the musl:0.1.16 of cross + "apt update && apt --assume-yes install libdbus-1-dev:$CROSS_DEB_ARCH libssl-dev:$CROSS_DEB_ARCH || exit 0", +] diff --git a/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs b/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs index 826c8e8b288..56e36b464f2 100644 --- a/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs +++ b/examples/rust/get_started/examples/11-attribute-based-authentication-control-plane.rs @@ -14,6 +14,7 @@ use ockam_api::nodes::NodeManager; use ockam_api::{multiaddr_to_route, multiaddr_to_transport_route}; use ockam_core::AsyncTryClone; use ockam_multiaddr::MultiAddr; +use ockam_transport_tcp::HostnamePort; /// This node supports a "control" server on which several "edge" devices can connect /// @@ -106,7 +107,7 @@ async fn start_node(ctx: Context, project_information_path: &str, token: OneTime // 4. create a tcp outlet with the above policy tcp.create_outlet( "outlet", - "127.0.0.1:5000", + HostnamePort::new("127.0.0.1", 5000), TcpOutletOptions::new() .with_incoming_access_control_impl(incoming_access_control) .with_outgoing_access_control_impl(outgoing_access_control), diff --git a/examples/rust/tcp_inlet_and_outlet/examples/01-inlet-outlet.rs b/examples/rust/tcp_inlet_and_outlet/examples/01-inlet-outlet.rs index 70127536674..51b91de48d0 100644 --- a/examples/rust/tcp_inlet_and_outlet/examples/01-inlet-outlet.rs +++ b/examples/rust/tcp_inlet_and_outlet/examples/01-inlet-outlet.rs @@ -1,4 +1,6 @@ use ockam::{node, route, Context, Result, TcpInletOptions, TcpOutletOptions, TcpTransportExtension}; +use ockam_transport_tcp::HostnamePort; +use std::str::FromStr; #[ockam::node] async fn main(ctx: Context) -> Result<()> { @@ -23,8 +25,12 @@ async fn main(ctx: Context) -> Result<()> { // a previous message from the Inlet. let outlet_target = std::env::args().nth(2).expect("no outlet target given"); - tcp.create_outlet("outlet", outlet_target, TcpOutletOptions::new()) - .await?; + tcp.create_outlet( + "outlet", + HostnamePort::from_str(&outlet_target)?, + TcpOutletOptions::new(), + ) + .await?; // Expect first command line argument to be the TCP address on which to start an Inlet // For example: 127.0.0.1:4001 diff --git a/examples/rust/tcp_inlet_and_outlet/examples/02-outlet.rs b/examples/rust/tcp_inlet_and_outlet/examples/02-outlet.rs index b3d53beebaa..3dc2a7a60b9 100644 --- a/examples/rust/tcp_inlet_and_outlet/examples/02-outlet.rs +++ b/examples/rust/tcp_inlet_and_outlet/examples/02-outlet.rs @@ -1,4 +1,6 @@ use ockam::{node, Context, Result, TcpListenerOptions, TcpOutletOptions, TcpTransportExtension}; +use ockam_transport_tcp::HostnamePort; +use std::str::FromStr; #[ockam::node] async fn main(ctx: Context) -> Result<()> { @@ -27,7 +29,7 @@ async fn main(ctx: Context) -> Result<()> { let outlet_target = std::env::args().nth(1).expect("no outlet target given"); tcp.create_outlet( "outlet", - outlet_target, + HostnamePort::from_str(&outlet_target)?, TcpOutletOptions::new().as_consumer(&tcp_listener_options.spawner_flow_control_id()), ) .await?; diff --git a/examples/rust/tcp_inlet_and_outlet/examples/03-outlet.rs b/examples/rust/tcp_inlet_and_outlet/examples/03-outlet.rs index 267dfcc4953..5b223c89b25 100644 --- a/examples/rust/tcp_inlet_and_outlet/examples/03-outlet.rs +++ b/examples/rust/tcp_inlet_and_outlet/examples/03-outlet.rs @@ -1,5 +1,7 @@ use ockam::identity::SecureChannelListenerOptions; use ockam::{node, Context, Result, TcpListenerOptions, TcpOutletOptions, TcpTransportExtension}; +use ockam_transport_tcp::HostnamePort; +use std::str::FromStr; #[ockam::node] async fn main(ctx: Context) -> Result<()> { @@ -40,7 +42,7 @@ async fn main(ctx: Context) -> Result<()> { let outlet_target = std::env::args().nth(1).expect("no outlet target given"); tcp.create_outlet( "outlet", - outlet_target, + HostnamePort::from_str(&outlet_target)?, TcpOutletOptions::new().as_consumer(&secure_channel_flow_control_id), ) .await?; diff --git a/examples/rust/tcp_inlet_and_outlet/examples/04-outlet.rs b/examples/rust/tcp_inlet_and_outlet/examples/04-outlet.rs index 3b82ef24711..4b5a93e0dc0 100644 --- a/examples/rust/tcp_inlet_and_outlet/examples/04-outlet.rs +++ b/examples/rust/tcp_inlet_and_outlet/examples/04-outlet.rs @@ -1,6 +1,8 @@ use ockam::identity::SecureChannelListenerOptions; use ockam::remote::RemoteRelayOptions; use ockam::{node, Context, Result, TcpConnectionOptions, TcpOutletOptions, TcpTransportExtension}; +use ockam_transport_tcp::HostnamePort; +use std::str::FromStr; #[ockam::node] async fn main(ctx: Context) -> Result<()> { @@ -37,7 +39,7 @@ async fn main(ctx: Context) -> Result<()> { let outlet_target = std::env::args().nth(1).expect("no outlet target given"); tcp.create_outlet( "outlet", - outlet_target, + HostnamePort::from_str(&outlet_target)?, TcpOutletOptions::new().as_consumer(&secure_channel_flow_control_id), ) .await?; diff --git a/implementations/rust/ockam/ockam_api/src/cli_state/tcp_portals.rs b/implementations/rust/ockam/ockam_api/src/cli_state/tcp_portals.rs new file mode 100644 index 00000000000..e69de29bb2d diff --git a/implementations/rust/ockam/ockam_api/src/kafka/integration_test.rs b/implementations/rust/ockam/ockam_api/src/kafka/integration_test.rs index 8c251261192..e6d07cd06f7 100644 --- a/implementations/rust/ockam/ockam_api/src/kafka/integration_test.rs +++ b/implementations/rust/ockam/ockam_api/src/kafka/integration_test.rs @@ -37,7 +37,7 @@ mod test { use ockam_multiaddr::proto::Service; use ockam_multiaddr::MultiAddr; use ockam_node::compat::tokio; - use ockam_transport_tcp::{TcpInletOptions, TcpOutletOptions}; + use ockam_transport_tcp::{HostnamePort, TcpInletOptions, TcpOutletOptions}; use crate::hop::Hop; use crate::kafka::protocol_aware::utils::{encode_request, encode_response}; @@ -147,7 +147,7 @@ mod test { .tcp .create_outlet( "kafka_consumer_outlet", - format!("127.0.0.1:{}", consumer_mock_kafka.port), + HostnamePort::new("127.0.0.1", consumer_mock_kafka.port), TcpOutletOptions::new(), ) .await?; @@ -167,7 +167,7 @@ mod test { .tcp .create_outlet( "kafka_producer_outlet", - format!("127.0.0.1:{}", producer_mock_kafka.port), + HostnamePort::new("127.0.0.1", producer_mock_kafka.port), TcpOutletOptions::new(), ) .await?; @@ -205,7 +205,7 @@ mod test { .tcp .create_outlet( "kafka_consumer_outlet", - format!("127.0.0.1:{}", consumer_mock_kafka.port), + HostnamePort::new("127.0.0.1", consumer_mock_kafka.port), TcpOutletOptions::new(), ) .await?; diff --git a/implementations/rust/ockam/ockam_api/src/kafka/outlet_controller.rs b/implementations/rust/ockam/ockam_api/src/kafka/outlet_controller.rs index 25d432537b6..8b19a227ea4 100644 --- a/implementations/rust/ockam/ockam_api/src/kafka/outlet_controller.rs +++ b/implementations/rust/ockam/ockam_api/src/kafka/outlet_controller.rs @@ -11,6 +11,7 @@ use ockam_core::errcode::{Kind, Origin}; use ockam_core::{route, Error}; use ockam_core::{Address, Result}; use ockam_node::Context; +use ockam_transport_tcp::HostnamePort; use std::net::SocketAddr; type BrokerId = i32; @@ -69,7 +70,8 @@ impl KafkaOutletController { worker_address: Address, policy_expression: Option, ) -> Result { - let mut payload = CreateOutlet::new(socket_address, Some(worker_address), false); + let hostname_port = HostnamePort::from_socket_addr(socket_address)?; + let mut payload = CreateOutlet::new(hostname_port, false, Some(worker_address), false); if let Some(expr) = policy_expression { payload.set_policy_expression(expr); } diff --git a/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs b/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs index fa5411df201..4ff02c9dae3 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/models/portal.rs @@ -12,6 +12,7 @@ use ockam::route; use ockam_abac::Expr; use ockam_core::{Address, IncomingAccessControl, OutgoingAccessControl, Route}; use ockam_multiaddr::MultiAddr; +use ockam_transport_tcp::HostnamePort; use serde::{Deserialize, Serialize}; use crate::error::ApiError; @@ -139,26 +140,30 @@ impl CreateInlet { #[cbor(map)] pub struct CreateOutlet { /// The address the portal should connect or bind to - #[n(1)] pub socket_addr: SocketAddr, + #[n(1)] pub hostname_port: HostnamePort, + /// If tls is true a TLS connection is established + #[n(2)] pub tls: bool, /// The address the portal should listen to - #[n(2)] pub worker_addr: Option
, + #[n(3)] pub worker_addr: Option
, /// Allow the outlet to be reachable from the default secure channel, useful when we want to /// tighten the flow control - #[n(3)] pub reachable_from_default_secure_channel: bool, + #[n(4)] pub reachable_from_default_secure_channel: bool, /// The expression for the access control policy for this outlet. /// If not set, the policy set for the [TCP outlet resource type](ockam_abac::ResourceType::TcpOutlet) /// will be used. - #[n(4)] pub policy_expression: Option, + #[n(5)] pub policy_expression: Option, } impl CreateOutlet { pub fn new( - socket_addr: SocketAddr, + hostname_port: HostnamePort, + tls: bool, worker_addr: Option
, reachable_from_default_secure_channel: bool, ) -> Self { Self { - socket_addr, + hostname_port, + tls, worker_addr, reachable_from_default_secure_channel, policy_expression: None, @@ -336,7 +341,7 @@ Outlet: #[rustfmt::skip] #[cbor(map)] pub struct InletList { - #[n(1)] pub list: Vec + #[n(1)] pub list: Vec, } impl InletList { @@ -350,7 +355,7 @@ impl InletList { #[rustfmt::skip] #[cbor(map)] pub struct OutletList { - #[n(1)] pub list: Vec + #[n(1)] pub list: Vec, } impl OutletList { diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs index 9a7150aef56..5f308944900 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/kafka_services.rs @@ -8,6 +8,7 @@ use ockam_core::compat::rand::random_string; use ockam_core::route; use ockam_multiaddr::proto::Project; use ockam_multiaddr::MultiAddr; +use ockam_transport_tcp::HostnamePort; use super::NodeManagerWorker; use crate::error::ApiError; @@ -141,13 +142,13 @@ impl NodeManagerWorker { Err(Response::not_found_no_request( &format!("Service at address '{address}' with kind {kind} not found"), )) - }, + } Ok(DeleteKafkaServiceResult::IncorrectKind { address, actual, expected }) => { Err(Response::not_found_no_request( &format!("Service at address '{address}' is not a kafka {expected}. A service of kind {actual} was found instead"), )) - }, - Err(e) => Err(Response::internal_error_no_request( &e.to_string())), + } + Err(e) => Err(Response::internal_error_no_request(&e.to_string())), } } } @@ -202,7 +203,8 @@ impl InMemoryNode { .await?; self.create_outlet( context, - bootstrap_server_addr, + HostnamePort::from_socket_addr(bootstrap_server_addr)?, + false, Some(KAFKA_OUTLET_BOOTSTRAP_ADDRESS.into()), false, OutletAccessControl::PolicyExpression(outlet_policy_expression.clone()), @@ -405,7 +407,8 @@ impl NodeManager { if let Err(e) = self .create_outlet( context, - bootstrap_server_addr, + HostnamePort::from_socket_addr(bootstrap_server_addr)?, + false, Some(KAFKA_OUTLET_BOOTSTRAP_ADDRESS.into()), false, OutletAccessControl::PolicyExpression(outlet_policy_expression), diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs index 82cac56eb56..f18b578638c 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/portals.rs @@ -15,7 +15,7 @@ use ockam_core::{async_trait, route, AsyncTryClone, Route}; use ockam_multiaddr::proto::Project as ProjectProto; use ockam_multiaddr::{MultiAddr, Protocol}; use ockam_node::Context; -use ockam_transport_tcp::{TcpInletOptions, TcpOutletOptions}; +use ockam_transport_tcp::{HostnamePort, TcpInletOptions, TcpOutletOptions}; use crate::error::ApiError; use crate::nodes::connection::Connection; @@ -111,17 +111,19 @@ impl NodeManagerWorker { create_outlet: CreateOutlet, ) -> Result, Response> { let CreateOutlet { - socket_addr, + hostname_port, worker_addr, reachable_from_default_secure_channel, policy_expression, + tls, } = create_outlet; match self .node_manager .create_outlet( ctx, - socket_addr, + hostname_port, + tls, worker_addr, reachable_from_default_secure_channel, OutletAccessControl::PolicyExpression(policy_expression), @@ -179,7 +181,8 @@ impl NodeManager { pub async fn create_outlet( &self, ctx: &Context, - socket_addr: SocketAddr, + hostname_port: HostnamePort, + tls: bool, worker_addr: Option
, reachable_from_default_secure_channel: bool, access_control: OutletAccessControl, @@ -190,9 +193,10 @@ impl NodeManager { .generate_worker_addr(worker_addr) .await; + let socket_addr = hostname_port.to_socket_addr()?; info!( "Handling request to create outlet portal at {:?} with worker {:?}", - socket_addr, worker_addr + &socket_addr, worker_addr ); // Check registry for a duplicated key @@ -224,7 +228,8 @@ impl NodeManager { let options = { let options = TcpOutletOptions::new() .with_incoming_access_control(incoming_ac) - .with_outgoing_access_control(outgoing_ac); + .with_outgoing_access_control(outgoing_ac) + .with_tls(tls); let options = if self.project_authority().is_none() { options.as_consumer(&self.api_transport_flow_control_id) } else { @@ -247,7 +252,7 @@ impl NodeManager { let res = self .tcp_transport - .create_tcp_outlet(worker_addr.clone(), socket_addr, options) + .create_tcp_outlet(worker_addr.clone(), hostname_port, options) .await; Ok(match res { @@ -803,7 +808,8 @@ pub trait Outlets { async fn create_outlet( &self, ctx: &Context, - to: &SocketAddr, + to: HostnamePort, + tls: bool, from: Option<&Address>, policy_expression: Option, ) -> miette::Result; @@ -811,15 +817,16 @@ pub trait Outlets { #[async_trait] impl Outlets for BackgroundNodeClient { - #[instrument(skip_all, fields(to = %to, from = ?from))] + #[instrument(skip_all, fields(to = % to, from = ? from))] async fn create_outlet( &self, ctx: &Context, - to: &SocketAddr, + to: HostnamePort, + tls: bool, from: Option<&Address>, policy_expression: Option, ) -> miette::Result { - let mut payload = CreateOutlet::new(*to, from.cloned(), true); + let mut payload = CreateOutlet::new(to, tls, from.cloned(), true); if let Some(policy_expression) = policy_expression { payload.set_policy_expression(policy_expression); } diff --git a/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs b/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs index a20d68f8ba2..ef1b56108bc 100644 --- a/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/test_utils/mod.rs @@ -1,4 +1,5 @@ #![allow(dead_code)] + use crate::config::lookup::InternetAddress; use crate::nodes::service::{NodeManagerCredentialRetrieverOptions, NodeManagerTrustOptions}; use ockam_node::{Context, NodeBuilder}; @@ -18,7 +19,7 @@ use ockam::identity::utils::AttributesBuilder; use ockam::identity::SecureChannels; use ockam::Result; use ockam_core::AsyncTryClone; -use ockam_transport_tcp::{TcpListenerOptions, TcpTransport}; +use ockam_transport_tcp::{HostnamePort, TcpListenerOptions, TcpTransport}; use crate::authenticator::credential_issuer::{DEFAULT_CREDENTIAL_VALIDITY, PROJECT_MEMBER_SCHEMA}; use crate::cli_state::{random_name, CliState}; @@ -127,8 +128,9 @@ pub async fn start_manager_for_tests( Ok(handle) } +#[derive(Debug, Clone)] pub struct EchoServerHandle { - pub chosen_addr: SocketAddr, + pub chosen_addr: HostnamePort, close: Arc, } @@ -189,7 +191,10 @@ pub async fn start_tcp_echo_server() -> EchoServerHandle { }); } - EchoServerHandle { chosen_addr, close } + EchoServerHandle { + chosen_addr: HostnamePort::from_socket_addr(chosen_addr).unwrap(), + close, + } } pub struct TestNode { diff --git a/implementations/rust/ockam/ockam_api/tests/latency.rs b/implementations/rust/ockam/ockam_api/tests/latency.rs index 85535d67661..a2b1c6e4656 100644 --- a/implementations/rust/ockam/ockam_api/tests/latency.rs +++ b/implementations/rust/ockam/ockam_api/tests/latency.rs @@ -17,7 +17,6 @@ use ockam_multiaddr::MultiAddr; /// In order for the result to be reliable, use the --profile release /// flag when running the tests. /// `cargo test --test latency --release -- --ignored --show-output` - #[ignore] #[test] pub fn measure_message_latency_two_nodes() { @@ -130,7 +129,8 @@ pub fn measure_buffer_latency_two_nodes_portal() { .node_manager .create_outlet( &second_node.context, - echo_server_handle.chosen_addr, + echo_server_handle.chosen_addr.clone(), + false, Some(Address::from_string("outlet")), true, OutletAccessControl::AccessControl((Arc::new(AllowAll), Arc::new(AllowAll))), diff --git a/implementations/rust/ockam/ockam_api/tests/portals.rs b/implementations/rust/ockam/ockam_api/tests/portals.rs index 2293fd8edcb..26cf64db92f 100644 --- a/implementations/rust/ockam/ockam_api/tests/portals.rs +++ b/implementations/rust/ockam/ockam_api/tests/portals.rs @@ -28,14 +28,18 @@ async fn inlet_outlet_local_successful(context: &mut Context) -> ockam::Result<( .node_manager .create_outlet( context, - echo_server_handle.chosen_addr, + echo_server_handle.chosen_addr.clone(), + false, Some(Address::from_string("outlet")), true, OutletAccessControl::AccessControl((Arc::new(AllowAll), Arc::new(AllowAll))), ) .await?; - assert_eq!(outlet_status.socket_addr, echo_server_handle.chosen_addr); + assert_eq!( + outlet_status.socket_addr, + echo_server_handle.chosen_addr.to_socket_addr()? + ); assert_eq!(outlet_status.worker_addr.address(), "outlet"); let inlet_status = node_manager_handle @@ -96,7 +100,8 @@ fn portal_node_goes_down_reconnect() { .node_manager .create_outlet( &second_node.context, - echo_server_handle.chosen_addr, + echo_server_handle.chosen_addr.clone(), + false, Some(Address::from_string("outlet")), true, OutletAccessControl::AccessControl((Arc::new(AllowAll), Arc::new(AllowAll))), @@ -160,7 +165,8 @@ fn portal_node_goes_down_reconnect() { .node_manager .create_outlet( &third_node.context, - echo_server_handle.chosen_addr, + echo_server_handle.chosen_addr.clone(), + false, Some(Address::from_string("outlet")), true, OutletAccessControl::AccessControl((Arc::new(AllowAll), Arc::new(AllowAll))), @@ -232,7 +238,8 @@ fn portal_low_bandwidth_connection_keep_working_for_60s() { .node_manager .create_outlet( &second_node.context, - echo_server_handle.chosen_addr, + echo_server_handle.chosen_addr.clone(), + false, Some(Address::from_string("outlet")), true, OutletAccessControl::AccessControl((Arc::new(AllowAll), Arc::new(AllowAll))), @@ -345,7 +352,8 @@ fn portal_heavy_load_exchanged() { .node_manager .create_outlet( &second_node.context, - echo_server_handle.chosen_addr, + echo_server_handle.chosen_addr.clone(), + false, Some(Address::from_string("outlet")), true, OutletAccessControl::AccessControl((Arc::new(AllowAll), Arc::new(AllowAll))), @@ -483,7 +491,8 @@ fn test_portal_payload_transfer(outgoing_disruption: Disruption, incoming_disrup .node_manager .create_outlet( &second_node.context, - echo_server_handle.chosen_addr, + echo_server_handle.chosen_addr.clone(), + false, Some(Address::from_string("outlet")), true, OutletAccessControl::AccessControl((Arc::new(AllowAll), Arc::new(AllowAll))), diff --git a/implementations/rust/ockam/ockam_api/tests/trace_context_propagation.rs b/implementations/rust/ockam/ockam_api/tests/trace_context_propagation.rs index 3bcc0795a3a..d0bedf03fe9 100644 --- a/implementations/rust/ockam/ockam_api/tests/trace_context_propagation.rs +++ b/implementations/rust/ockam/ockam_api/tests/trace_context_propagation.rs @@ -93,7 +93,7 @@ root ├── create tcp transport ├── TcpListenProcessor::start ├── create tcp transport - ├── TcpSendWorker::connect + ├── connect ├── TcpSendWorker::start ├── TcpRecvProcessor::start └── MessageSender::handle_message @@ -190,7 +190,7 @@ root ├── create tcp transport ├── TcpListenProcessor::start ├── create tcp transport - ├── TcpSendWorker::connect + ├── connect ├── TcpSendWorker::start ├── TcpRecvProcessor::start ├── TcpSendWorker::handle_message diff --git a/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/create.rs b/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/create.rs index edf32745daa..b1e2ee74503 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/create.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/create.rs @@ -4,7 +4,7 @@ use miette::{IntoDiagnostic, WrapErr}; use ockam_api::address::extract_address_value; use ockam_api::nodes::models::portal::OutletAccessControl; use ockam_core::Address; -use ockam_transport_tcp::resolve_peer; +use ockam_transport_tcp::{resolve_peer, HostnamePort}; use std::sync::Arc; use tracing::{debug, info}; @@ -36,7 +36,8 @@ impl AppState { match node_manager .create_outlet( &self.context(), - socket_addr, + HostnamePort::from_socket_addr(socket_addr)?, + false, Some(worker_addr.clone()), true, OutletAccessControl::AccessControl((Arc::new(incoming_ac), Arc::new(outgoing_ac))), diff --git a/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/state.rs b/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/state.rs index 0ad853f48eb..09c7dbbffe8 100644 --- a/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/state.rs +++ b/implementations/rust/ockam/ockam_app_lib/src/shared_service/tcp_outlet/state.rs @@ -6,6 +6,7 @@ use crate::incoming_services::PersistentIncomingService; use crate::state::{AppState, ModelState}; use ockam_api::nodes::models::portal::{OutletAccessControl, OutletStatus}; use ockam_core::Address; +use ockam_transport_tcp::HostnamePort; impl ModelState { pub fn add_tcp_outlet(&mut self, status: OutletStatus) { @@ -68,7 +69,9 @@ impl AppState { let _ = node_manager .create_outlet( &context, - tcp_outlet.socket_addr, + HostnamePort::from_socket_addr(tcp_outlet.socket_addr) + .expect("cannot parse the socket address as a hostname and port"), + false, Some(tcp_outlet.worker_addr.clone()), true, OutletAccessControl::AccessControl(( diff --git a/implementations/rust/ockam/ockam_command/src/run/parser/resource/tcp_outlets.rs b/implementations/rust/ockam/ockam_command/src/run/parser/resource/tcp_outlets.rs index ebd3f29b21b..f538cb26274 100644 --- a/implementations/rust/ockam/ockam_command/src/run/parser/resource/tcp_outlets.rs +++ b/implementations/rust/ockam/ockam_command/src/run/parser/resource/tcp_outlets.rs @@ -50,10 +50,8 @@ impl CommandsParser for TcpOutlets { #[cfg(test)] mod tests { - use std::net::SocketAddr; - use std::str::FromStr; - use super::*; + use ockam_transport_tcp::HostnamePort; #[test] fn tcp_outlet_config() { @@ -70,10 +68,10 @@ mod tests { let cmds = parsed.parse_commands(&ValuesOverrides::default()).unwrap(); assert_eq!(cmds.len(), 2); assert_eq!(cmds[0].from.clone().unwrap(), "to1"); - assert_eq!(cmds[0].to, SocketAddr::from_str("127.0.0.1:6060").unwrap()); + assert_eq!(cmds[0].to, HostnamePort::new("127.0.0.1", 6060)); assert_eq!(cmds[0].at.as_ref().unwrap(), "n"); assert_eq!(cmds[1].from.clone().unwrap(), "my_outlet"); - assert_eq!(cmds[1].to, SocketAddr::from_str("127.0.0.1:6061").unwrap()); + assert_eq!(cmds[1].to, HostnamePort::new("127.0.0.1", 6061)); assert!(cmds[1].at.is_none()); } } diff --git a/implementations/rust/ockam/ockam_command/src/tcp/outlet/create.rs b/implementations/rust/ockam/ockam_command/src/tcp/outlet/create.rs index 8f63d18b992..07cb6aa3e57 100644 --- a/implementations/rust/ockam/ockam_command/src/tcp/outlet/create.rs +++ b/implementations/rust/ockam/ockam_command/src/tcp/outlet/create.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::net::SocketAddr; +use std::str::FromStr; use async_trait::async_trait; use clap::Args; @@ -9,6 +9,7 @@ use opentelemetry::trace::FutureExt; use tokio::sync::Mutex; use tokio::try_join; +use crate::node::util::initialize_default_node; use crate::{docs, Command, CommandGlobalOpts}; use ockam::Context; use ockam_abac::Expr; @@ -21,9 +22,7 @@ use ockam_api::nodes::service::portals::Outlets; use ockam_api::nodes::BackgroundNodeClient; use ockam_api::{fmt_info, fmt_log, fmt_ok}; use ockam_core::Address; - -use crate::node::util::initialize_default_node; -use crate::util::parsers::socket_addr_parser; +use ockam_transport_tcp::HostnamePort; const AFTER_LONG_HELP: &str = include_str!("./static/create/after_long_help.txt"); const LONG_ABOUT: &str = include_str!("./static/create/long_about.txt"); @@ -31,13 +30,17 @@ const LONG_ABOUT: &str = include_str!("./static/create/long_about.txt"); /// Create a TCP Outlet that runs adjacent to a TCP server #[derive(Clone, Debug, Args)] #[command( - long_about = docs::about(LONG_ABOUT), - after_long_help = docs::after_help(AFTER_LONG_HELP) +long_about = docs::about(LONG_ABOUT), +after_long_help = docs::after_help(AFTER_LONG_HELP) )] pub struct CreateCommand { - /// TCP address where your TCP server is running. Your Outlet will send raw TCP traffic to it - #[arg(long, display_order = 900, id = "SOCKET_ADDRESS", value_parser = socket_addr_parser)] - pub to: SocketAddr, + /// TCP address where your TCP server is running: domain:port. Your Outlet will send raw TCP traffic to it + #[arg(long, display_order = 900, id = "HOSTNAME_PORT", value_parser = HostnamePort::from_str)] + pub to: HostnamePort, + + /// If tls is set then the outlet will establish a TLS connection over TCP + #[arg(long, display_order = 900, id = "BOOLEAN")] + pub tls: bool, /// Address of your TCP Outlet, which is part of a route that is used in other /// commands. This address must be unique. This address identifies the TCP Outlet @@ -74,7 +77,13 @@ impl Command for CreateCommand { let send_req = async { let from = self.from.map(Address::from); let res = node - .create_outlet(ctx, &self.to, from.as_ref(), self.policy_expression) + .create_outlet( + ctx, + self.to.clone(), + self.tls, + from.as_ref(), + self.policy_expression, + ) .await?; *is_finished.lock().await = true; Ok(res) diff --git a/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats b/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats index 06d44822e45..61b84602284 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/local/portals.bats @@ -185,6 +185,18 @@ teardown() { run_success curl -sS -m 20 -X POST "http://127.0.0.1:$port/upload" -F "files=@$OCKAM_HOME_BASE/.tmp/$tmp_dir_name/$file_name" } +@test "portals - create an inlet/outlet pair and move tcp traffic through it, where the outlet points to an HTTPs endpoint" { + port="$(random_port)" + run_success "$OCKAM" node create n1 + run_success "$OCKAM" node create n2 + + run_success "$OCKAM" tcp-outlet create --at /node/n1 --to google.com:443 + run_success "$OCKAM" tcp-inlet create --at /node/n2 --from "127.0.0.1:$port" --to /node/n1/service/outlet + + # This test does not pass on CI + # run_success curl --fail --head --max-time 10 "127.0.0.1:$port" +} + @test "portals - create an inlet/outlet pair with relay through a relay and move tcp traffic through it" { port="$(random_port)" run_success "$OCKAM" node create relay diff --git a/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml b/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml index 1371fbfaef3..42b006d9e5e 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml +++ b/implementations/rust/ockam/ockam_transport_tcp/Cargo.toml @@ -31,13 +31,18 @@ alloc = [] [dependencies] cfg-if = "1.0.0" hashbrown = { version = "0.14", default-features = false } +minicbor = "0.21" +native-tls = "0.2" ockam_core = { path = "../ockam_core", version = "^0.106.0" } ockam_macros = { path = "../ockam_macros", version = "^0.34.0" } ockam_node = { path = "../ockam_node", version = "^0.113.0" } ockam_transport_core = { path = "../ockam_transport_core", version = "^0.79.0" } opentelemetry = { version = "0.22.0", features = ["logs", "metrics", "trace"], optional = true } rand = "0.8" +regex = "1.10.3" +rustls-native-certs = "0.7" serde = { version = "1.0", default-features = false, features = ["derive"] } socket2 = { version = "0.5.6", features = ["all"] } tokio = { version = "1.37", features = ["rt-multi-thread", "sync", "net", "macros", "time", "io-util"] } +tokio-native-tls = "0.3" tracing = { version = "0.1", default-features = false } diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs b/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs index 1cdbc391f21..e8597ac4686 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/portal/inlet_listener.rs @@ -1,5 +1,5 @@ use crate::portal::addresses::{Addresses, PortalType}; -use crate::{portal::TcpPortalWorker, TcpInletOptions, TcpRegistry}; +use crate::{portal::TcpPortalWorker, HostnamePort, TcpInletOptions, TcpRegistry}; use ockam_core::compat::net::SocketAddr; use ockam_core::{async_trait, compat::boxed::Box}; use ockam_core::{Address, Processor, Result, Route}; @@ -94,12 +94,12 @@ impl Processor for TcpInletListenProcessor { outlet_listener_route.next()?, ); - let (stream, peer) = self.inner.accept().await.map_err(TransportError::from)?; + let (stream, socket_addr) = self.inner.accept().await.map_err(TransportError::from)?; TcpPortalWorker::start_new_inlet( ctx, self.registry.clone(), stream, - peer, + HostnamePort::from_socket_addr(socket_addr)?, outlet_listener_route, addresses, self.options.incoming_access_control.clone(), diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/portal/options.rs b/implementations/rust/ockam/ockam_transport_tcp/src/portal/options.rs index 74c2f36d9b7..f0103b47a71 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/portal/options.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/portal/options.rs @@ -83,6 +83,7 @@ pub struct TcpOutletOptions { pub(super) consumer: Vec, pub(super) incoming_access_control: Arc, pub(super) outgoing_access_control: Arc, + pub(super) tls: bool, } impl TcpOutletOptions { @@ -92,6 +93,7 @@ impl TcpOutletOptions { consumer: vec![], incoming_access_control: Arc::new(AllowAll), outgoing_access_control: Arc::new(AllowAll), + tls: false, } } @@ -113,6 +115,12 @@ impl TcpOutletOptions { self } + /// Set TLS + pub fn with_tls(mut self, tls: bool) -> Self { + self.tls = tls; + self + } + /// Set Outgoing Access Control pub fn with_outgoing_access_control_impl( mut self, diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/portal/outlet_listener.rs b/implementations/rust/ockam/ockam_transport_tcp/src/portal/outlet_listener.rs index c4576dcd7a4..a83ae5a9bb5 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/portal/outlet_listener.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/portal/outlet_listener.rs @@ -1,9 +1,8 @@ use crate::portal::addresses::{Addresses, PortalType}; -use crate::{portal::TcpPortalWorker, PortalMessage, TcpOutletOptions, TcpRegistry}; +use crate::{portal::TcpPortalWorker, HostnamePort, PortalMessage, TcpOutletOptions, TcpRegistry}; use ockam_core::{async_trait, Address, DenyAll, NeutralMessage, Result, Routed, Worker}; use ockam_node::{Context, WorkerBuilder}; use ockam_transport_core::TransportError; -use std::net::SocketAddr; use tracing::{debug, instrument}; /// A TCP Portal Outlet listen worker @@ -13,16 +12,16 @@ use tracing::{debug, instrument}; /// [`TcpTransport::create_outlet`](crate::TcpTransport::create_outlet). pub(crate) struct TcpOutletListenWorker { registry: TcpRegistry, - peer: SocketAddr, + hostname_port: HostnamePort, options: TcpOutletOptions, } impl TcpOutletListenWorker { /// Create a new `TcpOutletListenWorker` - fn new(registry: TcpRegistry, peer: SocketAddr, options: TcpOutletOptions) -> Self { + fn new(registry: TcpRegistry, hostname_port: HostnamePort, options: TcpOutletOptions) -> Self { Self { registry, - peer, + hostname_port, options, } } @@ -32,14 +31,14 @@ impl TcpOutletListenWorker { ctx: &Context, registry: TcpRegistry, address: Address, - peer: SocketAddr, + hostname_port: HostnamePort, options: TcpOutletOptions, ) -> Result<()> { let access_control = options.incoming_access_control.clone(); options.setup_flow_control_for_outlet_listener(ctx.flow_controls(), &address); - let worker = Self::new(registry, peer, options); + let worker = Self::new(registry, hostname_port, options); WorkerBuilder::new(worker) .with_address(address) .with_incoming_access_control_arc(access_control) @@ -93,7 +92,8 @@ impl Worker for TcpOutletListenWorker { TcpPortalWorker::start_new_outlet( ctx, self.registry.clone(), - self.peer, + self.hostname_port.clone(), + self.options.tls, return_route.clone(), addresses.clone(), self.options.incoming_access_control.clone(), diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_receiver.rs b/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_receiver.rs index 831b8c72763..6210a023038 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_receiver.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_receiver.rs @@ -9,7 +9,8 @@ use ockam_core::{route, Processor, Result}; use ockam_node::Context; use opentelemetry::global; use opentelemetry::trace::Tracer; -use tokio::{io::AsyncReadExt, net::tcp::OwnedReadHalf}; +use tokio::io::AsyncRead; +use tokio::io::AsyncReadExt; use tracing::{error, instrument, warn}; /// A TCP Portal receiving message processor @@ -17,20 +18,20 @@ use tracing::{error, instrument, warn}; /// TCP Portal receiving message processor are created by /// `TcpPortalWorker` after a call is made to /// [`TcpPortalWorker::start_receiver`](crate::TcpPortalWorker::start_receiver) -pub(crate) struct TcpPortalRecvProcessor { +pub(crate) struct TcpPortalRecvProcessor { registry: TcpRegistry, buf: Vec, - read_half: OwnedReadHalf, + read_half: R, addresses: Addresses, onward_route: Route, payload_packet_counter: u16, } -impl TcpPortalRecvProcessor { +impl TcpPortalRecvProcessor { /// Create a new `TcpPortalRecvProcessor` pub fn new( registry: TcpRegistry, - read_half: OwnedReadHalf, + read_half: R, addresses: Addresses, onward_route: Route, ) -> Self { @@ -46,7 +47,7 @@ impl TcpPortalRecvProcessor { } #[async_trait] -impl Processor for TcpPortalRecvProcessor { +impl Processor for TcpPortalRecvProcessor { type Context = Context; #[instrument(skip_all, name = "TcpPortalRecvProcessor::initialize")] diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_worker.rs b/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_worker.rs index 18d12e736d2..9cd0de68e7c 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_worker.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/portal/portal_worker.rs @@ -1,6 +1,11 @@ use crate::portal::addresses::{Addresses, PortalType}; -use crate::{portal::TcpPortalRecvProcessor, PortalInternalMessage, PortalMessage, TcpRegistry}; -use ockam_core::compat::{boxed::Box, net::SocketAddr, sync::Arc}; +use crate::portal::portal_worker::ReadHalfMaybeTls::{ReadHalfNoTls, ReadHalfWithTls}; +use crate::portal::portal_worker::WriteHalfMaybeTls::{WriteHalfNoTls, WriteHalfWithTls}; +use crate::transport::{connect, connect_tls}; +use crate::{ + portal::TcpPortalRecvProcessor, HostnamePort, PortalInternalMessage, PortalMessage, TcpRegistry, +}; +use ockam_core::compat::{boxed::Box, sync::Arc}; use ockam_core::{ async_trait, AllowOnwardAddress, AllowSourceAddress, Decodable, DenyAll, IncomingAccessControl, Mailbox, Mailboxes, OutgoingAccessControl, @@ -8,9 +13,10 @@ use ockam_core::{ use ockam_core::{Any, Result, Route, Routed, Worker}; use ockam_node::{Context, ProcessorBuilder, WorkerBuilder}; use ockam_transport_core::TransportError; -use tokio::io::AsyncWriteExt; +use tokio::io::{AsyncRead, AsyncWriteExt, ReadHalf, WriteHalf}; use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; use tokio::net::TcpStream; +use tokio_native_tls::TlsStream; use tracing::{debug, info, instrument, trace, warn}; /// Enumerate all `TcpPortalWorker` states @@ -36,15 +42,26 @@ enum State { pub(crate) struct TcpPortalWorker { registry: TcpRegistry, state: State, - write_half: Option, - read_half: Option, - peer: SocketAddr, + write_half: Option, + read_half: Option, + hostname_port: HostnamePort, addresses: Addresses, remote_route: Option, is_disconnecting: bool, portal_type: PortalType, last_received_packet_counter: u16, - outgoing_access_control: Arc, // To propagate to the receiver + outgoing_access_control: Arc, + is_tls: bool, +} + +enum ReadHalfMaybeTls { + ReadHalfNoTls(OwnedReadHalf), + ReadHalfWithTls(ReadHalf>), +} + +enum WriteHalfMaybeTls { + WriteHalfNoTls(OwnedWriteHalf), + WriteHalfWithTls(WriteHalf>), } impl TcpPortalWorker { @@ -55,20 +72,20 @@ impl TcpPortalWorker { ctx: &Context, registry: TcpRegistry, stream: TcpStream, - peer: SocketAddr, + hostname_port: HostnamePort, ping_route: Route, addresses: Addresses, incoming_access_control: Arc, - outgoing_access_control: Arc, + outgoing_access_control: Arc, // To propagate to the receiver ) -> Result<()> { Self::start( ctx, registry, - peer, + hostname_port, + false, State::SendPing { ping_route }, Some(stream), addresses, - PortalType::Inlet, incoming_access_control, outgoing_access_control, ) @@ -76,11 +93,13 @@ impl TcpPortalWorker { } /// Start a new `TcpPortalWorker` of type [`TypeName::Outlet`] + #[allow(clippy::too_many_arguments)] #[instrument(skip_all)] pub(super) async fn start_new_outlet( ctx: &Context, registry: TcpRegistry, - peer: SocketAddr, + hostname_port: HostnamePort, + tls: bool, pong_route: Route, addresses: Addresses, incoming_access_control: Arc, @@ -89,11 +108,11 @@ impl TcpPortalWorker { Self::start( ctx, registry, - peer, + hostname_port, + tls, State::SendPong { pong_route }, None, addresses, - PortalType::Outlet, incoming_access_control, outgoing_access_control, ) @@ -106,14 +125,19 @@ impl TcpPortalWorker { async fn start( ctx: &Context, registry: TcpRegistry, - peer: SocketAddr, + hostname_port: HostnamePort, + is_tls: bool, state: State, stream: Option, addresses: Addresses, - portal_type: PortalType, incoming_access_control: Arc, outgoing_access_control: Arc, ) -> Result<()> { + let portal_type = if stream.is_some() { + PortalType::Inlet + } else { + PortalType::Outlet + }; info!( "Creating new {:?} at sender remote: {}", portal_type.str(), @@ -121,24 +145,28 @@ impl TcpPortalWorker { ); let (rx, tx) = match stream { + // A TcpStream is provided in case of an inlet Some(s) => { + debug!("Connected to {} (with no TLS)", &hostname_port); let (rx, tx) = s.into_split(); - (Some(rx), Some(tx)) + (Some(ReadHalfNoTls(rx)), Some(WriteHalfNoTls(tx))) } None => (None, None), }; + debug!("The {} supports TLS: {}", portal_type.str(), is_tls); let worker = Self { registry, state, write_half: tx, read_half: rx, - peer, + hostname_port, addresses: addresses.clone(), remote_route: None, is_disconnecting: false, portal_type, last_received_packet_counter: u16::MAX, + is_tls, outgoing_access_control: outgoing_access_control.clone(), }; @@ -179,12 +207,23 @@ impl TcpPortalWorker { /// Start a `TcpPortalRecvProcessor` #[instrument(skip_all)] async fn start_receiver(&mut self, ctx: &Context, onward_route: Route) -> Result<()> { - let rx = if let Some(rx) = self.read_half.take() { - rx + if let Some(rx) = self.read_half.take() { + match rx { + ReadHalfNoTls(rx) => self.start_receive_processor(ctx, onward_route, rx).await, + ReadHalfWithTls(rx) => self.start_receive_processor(ctx, onward_route, rx).await, + } } else { - return Err(TransportError::PortalInvalidState)?; - }; + Err(TransportError::PortalInvalidState)? + } + } + /// Start a TcpPortalRecvProcessor using a specific AsyncRead implementation (either supporting TLS or not) + async fn start_receive_processor( + &mut self, + ctx: &Context, + onward_route: Route, + rx: R, + ) -> Result<()> { let receiver = TcpPortalRecvProcessor::new( self.registry.clone(), rx, @@ -323,13 +362,17 @@ impl TcpPortalWorker { // Should not happen return Err(TransportError::PortalInvalidState)?; } - - let stream = TcpStream::connect(self.peer) - .await - .map_err(TransportError::from)?; - let (rx, tx) = stream.into_split(); - self.write_half = Some(tx); - self.read_half = Some(rx); + if self.is_tls { + debug!("Connect to {} via TLS", &self.hostname_port); + let (rx, tx) = connect_tls(&self.hostname_port).await?; + self.write_half = Some(WriteHalfWithTls(tx)); + self.read_half = Some(ReadHalfWithTls(rx)); + } else { + debug!("Connect to {}", self.hostname_port); + let (rx, tx) = connect(self.hostname_port.to_socket_addr()?).await?; + self.write_half = Some(WriteHalfNoTls(tx)); + self.read_half = Some(ReadHalfNoTls(rx)); + } // Respond to Inlet before starting the processor but // after the connection has been established @@ -452,7 +495,7 @@ impl Worker for TcpPortalWorker { } } State::SendPing { .. } | State::SendPong { .. } => { - return Err(TransportError::PortalInvalidState)? + return Err(TransportError::PortalInvalidState)?; } } } @@ -494,16 +537,17 @@ impl TcpPortalWorker { return Err(TransportError::PortalInvalidState)?; }; - match tx.write_all(payload).await { - Ok(()) => {} - Err(err) => { - warn!( - "Failed to send message to peer {} with error: {}", - self.peer, err - ); - self.start_disconnection(ctx, DisconnectionReason::FailedTx) - .await?; - } + let result = match tx { + WriteHalfNoTls(tx) => tx.write_all(payload).await, + WriteHalfWithTls(tx) => tx.write_all(payload).await, + }; + if let Err(err) = result { + warn!( + "Failed to send message to peer {} with error: {}", + self.hostname_port, err + ); + self.start_disconnection(ctx, DisconnectionReason::FailedTx) + .await?; } Ok(()) diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/transport/common.rs b/implementations/rust/ockam/ockam_transport_tcp/src/transport/common.rs index 6208e7b91a4..838afc082bb 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/transport/common.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/transport/common.rs @@ -1,11 +1,22 @@ -use crate::TcpConnectionMode; +use crate::{HostnamePort, TcpConnectionMode}; +use cfg_if::cfg_if; use core::fmt; use core::fmt::Formatter; +use native_tls::Certificate; +use native_tls::TlsConnector as NativeTlsConnector; use ockam_core::compat::net::{SocketAddr, ToSocketAddrs}; +use ockam_core::errcode::{Kind, Origin}; use ockam_core::flow_control::FlowControlId; -use ockam_core::{Address, Result}; +use ockam_core::{Address, Error, Result}; use ockam_node::Context; use ockam_transport_core::TransportError; +use socket2::{SockRef, TcpKeepalive}; +use std::time::Duration; +use tokio::io::{ReadHalf, WriteHalf}; +use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; +use tokio::net::TcpStream; +use tokio_native_tls::TlsStream; +use tracing::{debug, instrument}; /// Result of [`TcpTransport::connect`] call. #[derive(Clone, Debug)] @@ -154,6 +165,108 @@ pub(super) fn parse_socket_addr(s: &str) -> Result { Ok(s.parse().map_err(|_| TransportError::InvalidAddress)?) } +/// Connect to a socket address via a regular TcpStream +#[instrument(skip_all)] +pub(crate) async fn connect(socket_address: SocketAddr) -> Result<(OwnedReadHalf, OwnedWriteHalf)> { + Ok(create_tcp_stream(socket_address).await?.into_split()) +} + +/// Create a TCP stream to a given socket address +pub(crate) async fn create_tcp_stream(socket_address: SocketAddr) -> Result { + debug!(addr = %socket_address, "Connecting"); + let connection = match TcpStream::connect(socket_address).await { + Ok(c) => { + debug!(addr = %socket_address, "Connected"); + c + } + Err(e) => { + debug!(addr = %socket_address, err = %e, "Failed to connect"); + return Err(TransportError::from(e))?; + } + }; + + let mut keepalive = TcpKeepalive::new() + .with_time(Duration::from_secs(300)) + .with_interval(Duration::from_secs(75)); + + cfg_if! { + if #[cfg(unix)] { + keepalive = keepalive.with_retries(2); + } + } + + let socket = SockRef::from(&connection); + socket + .set_tcp_keepalive(&keepalive) + .map_err(TransportError::from)?; + + Ok(connection) +} + +/// Connect to a socket address via a TlsStream +#[allow(clippy::type_complexity)] +#[instrument(skip_all)] +pub(crate) async fn connect_tls( + hostname_port: &HostnamePort, +) -> Result<( + ReadHalf>, + WriteHalf>, +)> { + let socket_address = hostname_port.to_socket_addr()?; + debug!(hostname_port = %hostname_port, addr = %socket_address, "Trying to connect using TLS"); + + // create a tcp stream + let connection = create_tcp_stream(socket_address).await?; + + // create a TLS connector + let tls_connector = create_tls_connector().await?; + + // Connect using TLS over TCP + let tls_stream = tls_connector + .connect(&hostname_port.hostname(), connection) + .await + .map_err(|e| { + Error::new( + Origin::Transport, + Kind::Io, + format!("Cannot connect using TLS to {hostname_port}: {e:?}"), + ) + })?; + debug!("Connected using TLS to {hostname_port}"); + Ok(tokio::io::split(tls_stream)) +} + +/// Create a TLS connector using the system certificates +pub(crate) async fn create_tls_connector() -> Result { + let mut native_tls_connector = NativeTlsConnector::builder(); + + let certificates = rustls_native_certs::load_native_certs().map_err(|e| { + Error::new( + Origin::Transport, + Kind::Io, + format!("Cannot load the native certificates: {e:?}"), + ) + })?; + debug!("there are {} certificates", certificates.len()); + + for certificate in certificates { + match Certificate::from_der(certificate.as_ref()) { + Ok(certificate) => { + native_tls_connector.add_root_certificate(certificate); + } + Err(e) => debug!("Cannot build create a certificate from a root system DER: {e:?}"), + } + } + let native_tls_connector = native_tls_connector.build().map_err(|e| { + Error::new( + Origin::Transport, + Kind::Io, + format!("Cannot build a native TLS connector: {e:?}"), + ) + })?; + Ok(tokio_native_tls::TlsConnector::from(native_tls_connector)) +} + #[cfg(test)] mod test { use crate::transport::common::parse_socket_addr; diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/transport/connection.rs b/implementations/rust/ockam/ockam_transport_tcp/src/transport/connection.rs index 5f1a005f37a..c2039bfd3c3 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/transport/connection.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/transport/connection.rs @@ -1,7 +1,9 @@ use crate::transport::common::{resolve_peer, TcpConnection}; +use crate::transport::connect; use crate::workers::{Addresses, TcpRecvProcessor, TcpSendWorker}; use crate::{TcpConnectionMode, TcpConnectionOptions, TcpTransport}; use ockam_core::{Address, Result}; +use tracing::debug; impl TcpTransport { /// Establish an outgoing TCP connection. @@ -21,10 +23,10 @@ impl TcpTransport { peer: impl Into, options: TcpConnectionOptions, ) -> Result { - // Resolve peer address - let socket = resolve_peer(peer.into())?; - - let (read_half, write_half) = TcpSendWorker::connect(socket).await?; + let peer = peer.into(); + let socket = resolve_peer(peer.clone())?; + debug!("Connecting to {}", peer.clone()); + let (read_half, write_half) = connect(socket).await?; let mode = TcpConnectionMode::Outgoing; let addresses = Addresses::generate(mode); diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/transport/hostname_port.rs b/implementations/rust/ockam/ockam_transport_tcp/src/transport/hostname_port.rs new file mode 100644 index 00000000000..2e044f92320 --- /dev/null +++ b/implementations/rust/ockam/ockam_transport_tcp/src/transport/hostname_port.rs @@ -0,0 +1,134 @@ +use crate::resolve_peer; +use core::fmt::{Display, Formatter}; +use core::str::FromStr; +use minicbor::{Decode, Encode}; +use ockam_core::errcode::{Kind, Origin}; +use std::net::SocketAddr; + +/// Hostname and port +#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] +pub struct HostnamePort { + #[n(0)] + hostname: String, + #[n(1)] + port: u16, +} + +impl HostnamePort { + /// Create a new HostnamePort + pub fn new(hostname: &str, port: u16) -> HostnamePort { + HostnamePort { + hostname: hostname.to_string(), + port, + } + } + + /// Return a hostname and port from a socket address + pub fn from_socket_addr(socket_addr: SocketAddr) -> ockam_core::Result { + HostnamePort::from_str(&socket_addr.to_string()) + } + + /// Return a socket address from a hostname and port + pub fn to_socket_addr(&self) -> ockam_core::Result { + resolve_peer(self.to_string()) + } + + /// Return the hostname + pub fn hostname(&self) -> String { + self.hostname.clone() + } + + /// Return the port + pub fn port(&self) -> u16 { + self.port + } +} + +impl FromStr for HostnamePort { + type Err = ockam_core::Error; + + /// Return a hostname and port when separated by a : + fn from_str(hostname_port: &str) -> ockam_core::Result { + // edge case: only the port is given + if let Ok(port) = hostname_port.parse::() { + return Ok(HostnamePort::new("127.0.0.1", port)); + } + + // otherwise check if brackets are present for an IP v6 address + let ip_regex = if hostname_port.contains('[') { + // we want to parse an IP v6 address as [hostname]:port where hostname does not contain [ or ] + regex::Regex::new(r"(\[[^\[\]].*\]):(\d+)").unwrap() + } else { + regex::Regex::new(r"^([^:]*):(\d+)$").unwrap() + }; + + // Attempt to match the regular expression + if let Some(captures) = ip_regex.captures(hostname_port) { + if let (Some(hostname), Some(port)) = (captures.get(1), captures.get(2)) { + if let Ok(port) = port.as_str().parse::() { + let mut hostname = hostname.as_str().to_string(); + if hostname.is_empty() { + hostname = "127.0.0.1".to_string() + }; + return Ok(HostnamePort { hostname, port }); + } + } + }; + + Err(ockam_core::Error::new( + Origin::Api, + Kind::Serialization, + format!("cannot read the value as hostname:port: {hostname_port}"), + )) + } +} + +impl Display for HostnamePort { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(&format!("{}:{}", self.hostname, self.port)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use core::str::FromStr; + + #[test] + fn test_hostname_port() -> ockam_core::Result<()> { + let actual = HostnamePort::from_str("localhost:80")?; + assert_eq!(actual, HostnamePort::new("localhost", 80)); + + let actual = HostnamePort::from_str("127.0.0.1:80")?; + assert_eq!(actual, HostnamePort::new("127.0.0.1", 80)); + + // this is malformed address + let actual = HostnamePort::from_str("127.0.0.1:80:80").ok(); + assert_eq!(actual, None); + + let actual = HostnamePort::from_str(":80")?; + assert_eq!(actual, HostnamePort::new("127.0.0.1", 80)); + + let actual = HostnamePort::from_str("80")?; + assert_eq!(actual, HostnamePort::new("127.0.0.1", 80)); + + let socket_addr = resolve_peer("76.76.21.21:8080".to_string()).unwrap(); + let actual = HostnamePort::from_socket_addr(socket_addr).ok(); + assert_eq!(actual, Some(HostnamePort::new("76.76.21.21", 8080))); + + let actual = HostnamePort::from_str("[2001:db8:85a3::8a2e:370:7334]:8080")?; + assert_eq!( + actual, + HostnamePort::new("[2001:db8:85a3::8a2e:370:7334]", 8080) + ); + + let socket_addr = SocketAddr::from_str("[2001:db8:85a3::8a2e:370:7334]:8080").unwrap(); + let actual = HostnamePort::from_socket_addr(socket_addr).ok(); + assert_eq!( + actual, + Some(HostnamePort::new("[2001:db8:85a3::8a2e:370:7334]", 8080)) + ); + + Ok(()) + } +} diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/transport/mod.rs b/implementations/rust/ockam/ockam_transport_tcp/src/transport/mod.rs index 375a1e2f2f7..5573de0a612 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/transport/mod.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/transport/mod.rs @@ -1,10 +1,12 @@ pub(crate) mod common; mod connection; +mod hostname_port; mod lifecycle; mod listener; mod portals; pub use common::*; +pub use hostname_port::*; pub use crate::portal::options::*; diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/transport/portals.rs b/implementations/rust/ockam/ockam_transport_tcp/src/transport/portals.rs index e6371cc8cc9..02c3840281b 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/transport/portals.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/transport/portals.rs @@ -1,6 +1,8 @@ use crate::portal::TcpInletListenProcessor; -use crate::transport::common::{parse_socket_addr, resolve_peer}; -use crate::{portal::TcpOutletListenWorker, TcpInletOptions, TcpOutletOptions, TcpTransport}; +use crate::transport::common::parse_socket_addr; +use crate::{ + portal::TcpOutletListenWorker, HostnamePort, TcpInletOptions, TcpOutletOptions, TcpTransport, +}; use core::fmt::Debug; use ockam_core::compat::net::SocketAddr; use ockam_core::{Address, Result, Route}; @@ -24,7 +26,7 @@ impl TcpTransport { /// # tcp.stop_inlet("inlet").await?; /// # Ok(()) } /// ``` - #[instrument(skip(self), fields(address = ?bind_addr.clone().into(), outlet_route = ?outlet_route.clone()))] + #[instrument(skip(self), fields(address = ? bind_addr.clone().into(), outlet_route = ? outlet_route.clone()))] pub async fn create_inlet( &self, bind_addr: impl Into + Clone + Debug, @@ -56,7 +58,7 @@ impl TcpTransport { /// tcp.stop_inlet("inlet").await?; /// # Ok(()) } /// ``` - #[instrument(skip(self), fields(address = ?addr.clone().into()))] + #[instrument(skip(self), fields(address = ? addr.clone().into()))] pub async fn stop_inlet(&self, addr: impl Into
+ Clone + Debug) -> Result<()> { self.ctx.stop_processor(addr).await?; @@ -70,30 +72,29 @@ impl TcpTransport { /// Pair of corresponding Inlet and Outlet is called Portal. /// /// ```rust - /// use ockam_transport_tcp::{TcpOutletOptions, TcpTransport}; + /// use ockam_transport_tcp::{HostnamePort, TcpOutletOptions, TcpTransport}; /// # use ockam_node::Context; /// # use ockam_core::{AllowAll, Result}; /// # async fn test(ctx: Context) -> Result<()> { /// /// let tcp = TcpTransport::create(&ctx).await?; - /// tcp.create_outlet("outlet", "localhost:9000", TcpOutletOptions::new()).await?; + /// tcp.create_outlet("outlet", HostnamePort::new("localhost", 9000), TcpOutletOptions::new()).await?; /// # tcp.stop_outlet("outlet").await?; /// # Ok(()) } /// ``` - #[instrument(skip(self), fields(address = ?address.clone().into(), peer = ?peer.clone().into()))] + #[instrument(skip(self), fields(address = ? address.clone().into(), peer = ? hostname_port.clone()))] pub async fn create_outlet( &self, address: impl Into
+ Clone + Debug, - peer: impl Into + Clone, + hostname_port: HostnamePort, options: TcpOutletOptions, ) -> Result<()> { - // Resolve peer address - let peer_addr = resolve_peer(peer.into())?; + // Resolve peer address as a socket address TcpOutletListenWorker::start( &self.ctx, self.registry.clone(), address.into(), - peer_addr, + hostname_port, options, ) .await?; @@ -106,29 +107,35 @@ impl TcpTransport { pub async fn create_tcp_outlet( &self, address: Address, - peer: SocketAddr, + hostname_port: HostnamePort, options: TcpOutletOptions, ) -> Result<()> { - TcpOutletListenWorker::start(&self.ctx, self.registry.clone(), address, peer, options) - .await?; + TcpOutletListenWorker::start( + &self.ctx, + self.registry.clone(), + address, + hostname_port, + options, + ) + .await?; Ok(()) } /// Stop outlet at addr /// ```rust - /// use ockam_transport_tcp::{TcpOutletOptions, TcpTransport}; + /// use ockam_transport_tcp::{HostnamePort, TcpOutletOptions, TcpTransport}; /// # use ockam_node::Context; /// # use ockam_core::{AllowAll, Result}; /// # async fn test(ctx: Context) -> Result<()> { - /// const TARGET_PEER: &str = "127.0.0.1:5000"; + /// let target_peer = HostnamePort::new("127.0.0.1", 5000); /// /// let tcp = TcpTransport::create(&ctx).await?; - /// tcp.create_outlet("outlet", TARGET_PEER, TcpOutletOptions::new()).await?; + /// tcp.create_outlet("outlet", target_peer, TcpOutletOptions::new()).await?; /// tcp.stop_outlet("outlet").await?; /// # Ok(()) } /// ``` - #[instrument(skip(self), fields(address = %addr.clone().into()))] + #[instrument(skip(self), fields(address = % addr.clone().into()))] pub async fn stop_outlet(&self, addr: impl Into
+ Clone + Debug) -> Result<()> { self.ctx.stop_worker(addr).await?; Ok(()) diff --git a/implementations/rust/ockam/ockam_transport_tcp/src/workers/sender.rs b/implementations/rust/ockam/ockam_transport_tcp/src/workers/sender.rs index 9d35a81f4aa..f7a8023f22d 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/src/workers/sender.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/src/workers/sender.rs @@ -1,7 +1,5 @@ use crate::workers::Addresses; use crate::{TcpConnectionMode, TcpRegistry, TcpSenderInfo}; -use cfg_if::cfg_if; -use core::time::Duration; use ockam_core::flow_control::FlowControlId; use ockam_core::{ async_trait, @@ -10,14 +8,12 @@ use ockam_core::{ }; use ockam_core::{Any, Decodable, Mailbox, Mailboxes, Message, Result, Routed, Worker}; use ockam_node::{Context, WorkerBuilder}; -use ockam_transport_core::{encode_transport_message, TransportError}; +use ockam_transport_core::encode_transport_message; use serde::{Deserialize, Serialize}; -use socket2::{SockRef, TcpKeepalive}; use tokio::io::AsyncWriteExt; -use tokio::net::tcp::{OwnedReadHalf, OwnedWriteHalf}; -use tokio::net::TcpStream; -use tracing::{debug, info, instrument, trace, warn}; +use tokio::net::tcp::OwnedWriteHalf; +use tracing::{info, instrument, trace, warn}; #[derive(Serialize, Deserialize, Message, Clone)] pub(crate) enum TcpSendWorkerMsg { @@ -119,38 +115,6 @@ impl TcpSendWorker { Ok(()) } - - #[instrument(skip_all, name = "TcpSendWorker::connect")] - pub(crate) async fn connect( - socket_address: SocketAddr, - ) -> Result<(OwnedReadHalf, OwnedWriteHalf)> { - debug!(addr = %socket_address, "Connecting"); - let connection = match TcpStream::connect(socket_address).await { - Ok(c) => { - debug!(addr = %socket_address, "Connected"); - c - } - Err(e) => { - debug!(addr = %socket_address, err = %e, "Failed to connect"); - return Err(TransportError::from(e))?; - } - }; - - let mut keepalive = TcpKeepalive::new() - .with_time(Duration::from_secs(300)) - .with_interval(Duration::from_secs(75)); - - cfg_if! { - if #[cfg(unix)] { - keepalive = keepalive.with_retries(2); - } - } - - let socket = SockRef::from(&connection); - socket.set_tcp_keepalive(&keepalive).unwrap(); - - Ok(connection.into_split()) - } } #[async_trait] diff --git a/implementations/rust/ockam/ockam_transport_tcp/tests/portal.rs b/implementations/rust/ockam/ockam_transport_tcp/tests/portal.rs index 9b40a3fe46d..64f8d0b1bbd 100644 --- a/implementations/rust/ockam/ockam_transport_tcp/tests/portal.rs +++ b/implementations/rust/ockam/ockam_transport_tcp/tests/portal.rs @@ -1,3 +1,4 @@ +use std::str::FromStr; use std::time::Duration; use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -7,7 +8,8 @@ use ockam_core::compat::rand::random; use ockam_core::{route, Result}; use ockam_node::Context; use ockam_transport_tcp::{ - TcpConnectionOptions, TcpInletOptions, TcpListenerOptions, TcpOutletOptions, TcpTransport, + HostnamePort, TcpConnectionOptions, TcpInletOptions, TcpListenerOptions, TcpOutletOptions, + TcpTransport, }; const LENGTH: usize = 32; @@ -18,7 +20,9 @@ async fn setup(ctx: &Context) -> Result<(String, TcpListener)> { let listener = { let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); let bind_address = listener.local_addr().unwrap().to_string(); - tcp.create_outlet("outlet", bind_address.clone(), TcpOutletOptions::new()) + let hostname_port = HostnamePort::from_str(&bind_address)?; + + tcp.create_outlet("outlet", hostname_port, TcpOutletOptions::new()) .await?; listener }; @@ -136,7 +140,7 @@ async fn portal__tcp_connection__should_succeed(ctx: &mut Context) -> Result<()> let bind_address = listener.local_addr().unwrap().to_string(); tcp.create_outlet( "outlet", - bind_address.clone(), + HostnamePort::from_str(&bind_address)?, TcpOutletOptions::new().as_consumer(&outlet_flow_control_id), ) .await?; @@ -191,7 +195,7 @@ async fn portal__tcp_connection_with_invalid_message_flow__should_not_succeed( tcp.create_outlet( "outlet_invalid", - bind_address.clone(), + HostnamePort::from_str(&bind_address)?, TcpOutletOptions::new(), ) .await?; diff --git a/tools/cross/Cross.Dockerfile.armv7 b/tools/cross/Cross.Dockerfile.armv7 index 19b7cb95263..3bb92390a22 100644 --- a/tools/cross/Cross.Dockerfile.armv7 +++ b/tools/cross/Cross.Dockerfile.armv7 @@ -1,5 +1,13 @@ FROM ghcr.io/cross-rs/armv7-unknown-linux-musleabihf@sha256:dd17a0ad95a3fce9c15230f60f8ed3bf6e60d81de260fbb9e457e4a49de8a43a ARG DEBIAN_FRONTEND=noninteractive +RUN dpkg --add-architecture armhf RUN apt-get update && \ - apt-get install --assume-yes libdbus-1-dev + apt-get install --assume-yes libdbus-1-dev tree pkg-config openssl libssl-dev libssl-dev:armhf +RUN echo dpkg +RUN dpkg -L openssl +RUN echo dpkg +RUN tree /usr/include +RUN tree /usr/lib +ENV OPENSSL_INCLUDE_DIR /usr/include/openssl +ENV OPENSSL_INCLUDE_DIR /usr/include/openssl