├── .gitattributes ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── golangci-lint.yml │ ├── main.yml │ ├── mattermost-channel-posts.yml │ ├── mattermost-webhook.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASING.md ├── SECURITY.md ├── assets └── Go SDK.png ├── edge-apis ├── authwrapper.go ├── clients.go ├── component.go ├── credentials.go ├── oidc.go ├── pool.go └── urls.go ├── edgexg └── impl.go ├── example ├── README.md ├── chat-p2p │ ├── README.md │ ├── chat-p2p.go │ ├── setup.go │ └── setup.md ├── chat │ ├── README.md │ ├── chat-client │ │ └── chat-client.go │ └── chat-server │ │ └── chat-server.go ├── curlz │ ├── README.md │ ├── curlz.go │ ├── unzitified.png │ └── zitified.png ├── go.mod ├── go.sum ├── grpc-example │ ├── README.md │ ├── grpc-client │ │ └── main.go │ └── grpc-server │ │ └── main.go ├── http-client │ ├── README.md │ └── main.go ├── influxdb-client-go │ ├── go.mod │ ├── go.sum │ └── main.go ├── jwtchat │ ├── README.md │ ├── jwtchat-client │ │ └── client.go │ ├── jwtchat-idp │ │ ├── exampleop │ │ │ ├── login.go │ │ │ └── op.go │ │ ├── main.go │ │ └── storage │ │ │ ├── client.go │ │ │ ├── oidc.go │ │ │ ├── storage.go │ │ │ ├── token.go │ │ │ └── user.go │ └── jwtchat-server │ │ └── server.go ├── reflect │ ├── Dockerfile │ ├── README.md │ ├── cmd │ │ ├── client.go │ │ ├── pkg-vars.go │ │ └── server.go │ ├── docker-compose.yml │ └── main.go ├── simple-server │ ├── README.md │ └── simple-server.go ├── udp-offload │ ├── README.md │ ├── udp-offload-client │ │ └── main.go │ └── udp-server │ │ └── udp-server.go ├── zcat │ ├── README.md │ └── zcat.go └── zping │ ├── README.md │ ├── client.go │ ├── enroll.go │ ├── main.go │ ├── network.png │ ├── root.go │ └── server.go ├── exercises ├── README.md └── http │ ├── README.md │ ├── client │ ├── before │ │ └── simple-client.go │ └── zitified │ │ └── simple-client.go │ ├── server │ ├── before │ │ └── simple-server.go │ └── zitified │ │ └── simple-server.go │ ├── simple-cloud.png │ ├── simple-example.png │ └── simple-zitified-example.png ├── expected.licenses ├── go.mod ├── go.sum ├── http_transport.go ├── inspect └── inspect.go ├── license-check.sh ├── pb └── edge_client_pb │ ├── README.md │ ├── edge_client.pb.go │ ├── edge_client.proto │ └── generate.go ├── sdk-version └── sdk-version.go ├── version ├── xgress ├── circuit_inspections.go ├── decoder.go ├── heartbeat_transformer.go ├── link_receive_buffer.go ├── link_send_buffer.go ├── messages.go ├── messages_test.go ├── metrics.go ├── minimal_payload_test.go ├── options.go ├── ordering_test.go ├── payload_ingester.go ├── retransmitter.go └── xgress.go └── ziti ├── client.go ├── collection.go ├── config.go ├── contexts.go ├── default_collection.go ├── dialer.go ├── edge ├── addr.go ├── addr_parsers.go ├── addr_parsers_js.go ├── channel.go ├── conn.go ├── messages.go ├── msg_mux.go ├── network │ ├── conn.go │ ├── conn_test.go │ ├── factory.go │ ├── listener.go │ ├── msg_timer.go │ ├── seq.go │ ├── seq_test.go │ └── xg_adapter.go ├── posture │ ├── cache.go │ ├── domain.go │ ├── domain_windows.go │ ├── mac.go │ ├── os.go │ ├── posture_windows_test.go │ ├── process.go │ ├── process_js.go │ ├── process_notjs.go │ ├── process_notwin.go │ └── process_windows.go └── types.go ├── enroll └── enroll.go ├── events.go ├── key_alg_var.go ├── options.go ├── sdkinfo ├── build_info.go ├── host.go ├── host_js.go ├── host_unix.go └── host_windows.go ├── signing ├── signing.go └── signing_test.go ├── terminators.go ├── token.go ├── xg_env.go ├── ziti.go └── ziti_test.go /.gitattributes: -------------------------------------------------------------------------------- 1 | *.txt eol=lf 2 | *.gitignore text eol=lf 3 | *.sh text eol=lf 4 | *.md text eol=lf 5 | *.mod text eol=lf 6 | *.sum text eol=lf 7 | *.go text eol=lf 8 | *.yml text eol=lf 9 | *.proto text eol=lf 10 | *.json text eol=lf 11 | *.html text eol=lf 12 | *.svg text eol=lf 13 | *.js text eol=lf 14 | *.css text eol=lf 15 | *.yaml text eol=lf 16 | *.http text eol=lf 17 | *.ps1 text eol=lf 18 | *.g4 text eol=lf 19 | *.interp text eol=lf 20 | *.pem text eol=lf 21 | *.cnf text eol=lf 22 | *.conf text eol=lf 23 | *.gitmodules text eol=lf 24 | *.variants text eol=lf 25 | *.cmake text eol=lf 26 | *.bat text eol=lf 27 | *.env text eol=lf 28 | *.service text eol=lf 29 | *.tmpl text eol=lf 30 | *.partial text eol=lf 31 | *.liquid text eol=lf 32 | *.tokens text eol=lf 33 | *.attr text eol=lf 34 | *.in text eol=lf 35 | *.h text eol=lf 36 | *.c text eol=lf 37 | *.kts text eol=lf 38 | *.properties text eol=lf 39 | *.rst text eol=lf 40 | *.gradle text eol=lf 41 | *.java text eol=lf 42 | *.kt text eol=lf -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # see: 2 | # https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/creating-a-repository-on-github/about-code-owners 3 | * @openziti/sig-core 4 | 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: gomod 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | groups: 9 | non-major: 10 | applies-to: version-updates 11 | update-types: 12 | - "minor" 13 | - "patch" 14 | 15 | - package-ecosystem: github-actions 16 | directory: "/" 17 | schedule: 18 | interval: weekly 19 | open-pull-requests-limit: 10 20 | groups: 21 | all: 22 | applies-to: version-updates 23 | update-types: 24 | - "major" 25 | - "minor" 26 | - "patch" 27 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | pull_request: 4 | permissions: 5 | contents: read 6 | # Optional: allow read access to pull request. Use with `only-new-issues` option. 7 | # pull-requests: read 8 | 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/setup-go@v5 15 | with: 16 | go-version: stable 17 | - uses: actions/checkout@v4 18 | - name: golangci-lint 19 | uses: golangci/golangci-lint-action@v6 20 | with: 21 | # Optional: version of golangci-lint to use in form of v1.2 or v1.2.3 or `latest` to use the latest version 22 | version: latest 23 | 24 | # Optional: working directory, useful for monorepos 25 | # working-directory: somedir 26 | 27 | # Optional: golangci-lint command line arguments. 28 | # args: --issues-exit-code=0 29 | 30 | # Optional: show only new issues if it's a pull request. The default value is `false`. 31 | # only-new-issues: true 32 | 33 | # Optional: if set to true then the all caching functionality will be complete disabled, 34 | # takes precedence over all other caching options. 35 | # skip-cache: true 36 | 37 | # Optional: if set to true then the action don't cache or restore ~/go/pkg. 38 | # skip-pkg-cache: true 39 | 40 | # Optional: if set to true then the action don't cache or restore ~/.cache/go-build. 41 | # skip-build-cache: true 42 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - release-* 8 | pull_request: 9 | workflow_dispatch: 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Git Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | persist-credentials: false 19 | 20 | - name: Install Go 21 | uses: actions/setup-go@v5 22 | with: 23 | go-version: stable 24 | 25 | - name: Install Ziti CI 26 | uses: openziti/ziti-ci@v1 27 | 28 | - name: Build and Test 29 | run: | 30 | go test ./... 31 | go test -C example ./... 32 | go install ./... 33 | ./license-check.sh 34 | $(go env GOPATH)/bin/ziti-ci verify-version $($(go env GOPATH)/bin/sdk-version) 35 | -------------------------------------------------------------------------------- /.github/workflows/mattermost-channel-posts.yml: -------------------------------------------------------------------------------- 1 | name: mattermost-ziti-webhook 2 | on: 3 | issues: 4 | issue_comment: 5 | pull_request_review: 6 | types: [ submitted ] 7 | pull_request_review_comment: 8 | pull_request: 9 | types: [ opened, reopened, ready_for_review, closed ] 10 | fork: 11 | push: 12 | tags: 13 | - '*' 14 | release: 15 | types: [ released ] 16 | workflow_dispatch: 17 | watch: 18 | types: [ started ] 19 | 20 | jobs: 21 | send-notifications: 22 | runs-on: ubuntu-latest 23 | name: POST Webhook 24 | if: github.actor != 'dependabot[bot]' 25 | steps: 26 | - uses: openziti/ziti-mattermost-action-py@main 27 | if: | 28 | github.repository_owner == 'openziti' 29 | && ((github.event_name != 'pull_request_review') 30 | || (github.event_name == 'pull_request_review' && github.event.review.state == 'approved')) 31 | with: 32 | zitiId: ${{ secrets.ZITI_MATTERMOST_IDENTITY }} 33 | webhookUrl: ${{ secrets.ZHOOK_URL }} 34 | eventJson: ${{ toJson(github.event) }} 35 | senderUsername: "GitHubZ" 36 | destChannel: "dev-notifications" 37 | 38 | - uses: openziti/ziti-mattermost-action-py@main 39 | if: | 40 | github.repository_owner == 'openziti' 41 | && ((github.event_name != 'pull_request_review') 42 | || (github.event_name == 'pull_request_review' && github.event.review.state == 'approved')) 43 | with: 44 | zitiId: ${{ secrets.ZITI_MATTERMOST_IDENTITY }} 45 | webhookUrl: ${{ secrets.ZHOOK_URL }} 46 | eventJson: ${{ toJson(github.event) }} 47 | senderUsername: "GitHubZ" 48 | destChannel: "github-sig-core" 49 | 50 | -------------------------------------------------------------------------------- /.github/workflows/mattermost-webhook.yml: -------------------------------------------------------------------------------- 1 | name: mattermost-ziti-webhook 2 | on: 3 | create: 4 | delete: 5 | issues: 6 | issue_comment: 7 | pull_request_review: 8 | pull_request_review_comment: 9 | pull_request: 10 | push: 11 | fork: 12 | release: 13 | 14 | jobs: 15 | mattermost-ziti-webhook: 16 | runs-on: ubuntu-latest 17 | name: POST Webhook 18 | if: github.actor != 'dependabot[bot]' 19 | env: 20 | ZITI_LOG: 99 21 | ZITI_NODEJS_LOG: 99 22 | steps: 23 | - uses: openziti/ziti-webhook-action@main 24 | with: 25 | ziti-id: ${{ secrets.ZITI_MATTERMOST_IDENTITY }} 26 | webhook-url: ${{ secrets.ZITI_MATTERMOST_WEBHOOK_URL }} 27 | webhook-secret: ${{ secrets.ZITI_MATTERMOSTI_WEBHOOK_SECRET }} 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | # builds on vX.X.X PUSH to main only. Generates versioned binaries, vX.X.X tags, and GitHub releases 2 | name: release-builds 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | jobs: 9 | release: 10 | name: Release 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Git Checkout 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Install Go 19 | uses: actions/setup-go@v5 20 | with: 21 | go-version-file: ./go.mod 22 | 23 | - name: Install Ziti CI 24 | uses: openziti/ziti-ci@v1 25 | 26 | - name: Test 27 | run: | 28 | go test ./... 29 | 30 | - name: Create Release Notes 31 | run: | 32 | $(go env GOPATH)/bin/ziti-ci get-release-notes CHANGELOG.md > changelog.tmp 33 | 34 | - name: Create Release w/ Notes 35 | id: create_release 36 | uses: actions/create-release@v1 37 | env: 38 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 39 | with: 40 | tag_name: ${{ github.ref }} 41 | release_name: Release ${{ github.ref }} 42 | draft: false 43 | prerelease: false 44 | body_path: changelog.tmp 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | github_deploy_key 3 | *.json 4 | *.jwt 5 | go.work 6 | go.work.sum 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | All open source projects managed by OpenZiti share a common [code of conduct](https://docs.openziti.io/policies/CODE_OF_CONDUCT.html) which all contributors are expected to follow. Please be sure you read, understand and adhere to the guidelines expressed therein. 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | NetFoundry welcomes all and any contributions. All open source projects managed by NetFoundry share a common 4 | [guide for contributions](https://netfoundry.github.io/policies/CONTRIBUTING.html). 5 | 6 | If you are eager to contribute to a NetFoundry-managed open source project please read and act accordingly. 7 | -------------------------------------------------------------------------------- /RELEASING.md: -------------------------------------------------------------------------------- 1 | # How to release the OpenZiti SDK for Go 2 | 3 | As part of your PR, do the following: 4 | 5 | * Install `ziti-ci` 6 | * `go install github.com/openziti/ziti-ci@v0.5.125` (or latest) 7 | * Make sure the buildinfo is up to date using: 8 | * `ziti-ci update-sdk-build-info` 9 | * This will update the version number in the code 10 | * Make sure the release notes are up to date using: 11 | * `ziti-ci build-sdk-release-notes` 12 | * This will emit the standard release notes to stdout. The release notes can be copied into the CHANGELOG.md and edited as necessary 13 | 14 | Once your PR is merged and you wish to do a release: 15 | 16 | 1. Make sure you're on main and have the latest code 17 | 1. `git checkout main` 18 | 1. `git pull` 19 | 1. Tag the release 20 | 1. `git tag -s -m "Release "` 21 | 1. Push the tag: `git push origin ` 22 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | Please refer to the [openziti-security repository](https://github.com/openziti/openziti-security) for details of the security policies and processes for this repository. -------------------------------------------------------------------------------- /assets/Go SDK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/ad526d40ec0833d7fc589790e97243b6131c4bf8/assets/Go SDK.png -------------------------------------------------------------------------------- /edge-apis/component.go: -------------------------------------------------------------------------------- 1 | package edge_apis 2 | 3 | import ( 4 | "crypto/x509" 5 | "github.com/openziti/edge-api/rest_util" 6 | "net/http" 7 | "net/http/cookiejar" 8 | "net/url" 9 | "time" 10 | ) 11 | 12 | // Components provides the basic shared lower level pieces used to assemble go-swagger/openapi clients. These 13 | // components are interconnected and have references to each other. This struct is used to set, move, and manage 14 | // them as a set. 15 | type Components struct { 16 | HttpClient *http.Client 17 | HttpTransport *http.Transport 18 | CaPool *x509.CertPool 19 | } 20 | 21 | type ComponentsConfig struct { 22 | Proxy func(*http.Request) (*url.URL, error) 23 | } 24 | 25 | // NewComponents assembles a new set of components with reasonable production defaults. 26 | func NewComponents() *Components { 27 | return NewComponentsWithConfig(&ComponentsConfig{ 28 | Proxy: http.ProxyFromEnvironment, 29 | }) 30 | } 31 | 32 | // NewComponentsWithConfig assembles a new set of components with reasonable production defaults. 33 | func NewComponentsWithConfig(cfg *ComponentsConfig) *Components { 34 | tlsClientConfig, _ := rest_util.NewTlsConfig() 35 | 36 | httpTransport := &http.Transport{ 37 | TLSClientConfig: tlsClientConfig, 38 | ForceAttemptHTTP2: true, 39 | MaxIdleConns: 10, 40 | IdleConnTimeout: 10 * time.Second, 41 | TLSHandshakeTimeout: 10 * time.Second, 42 | ExpectContinueTimeout: 1 * time.Second, 43 | } 44 | 45 | if cfg != nil && cfg.Proxy != nil { 46 | httpTransport.Proxy = cfg.Proxy 47 | } 48 | 49 | jar, _ := cookiejar.New(nil) 50 | 51 | httpClient := &http.Client{ 52 | Transport: httpTransport, 53 | CheckRedirect: nil, 54 | Jar: jar, 55 | Timeout: 10 * time.Second, 56 | } 57 | 58 | return &Components{ 59 | HttpClient: httpClient, 60 | HttpTransport: httpTransport, 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /edge-apis/urls.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package edge_apis 18 | 19 | import ( 20 | "strings" 21 | ) 22 | 23 | const ( 24 | ClientApiPath = "/edge/client/v1" 25 | ManagementApiPath = "/edge/management/v1" 26 | ) 27 | 28 | // ClientUrl returns a URL with the given hostname in the format of `https:///edge/management/v1`. 29 | // The hostname provided may include a port. 30 | func ClientUrl(hostname string) string { 31 | return concat(hostname, ClientApiPath) 32 | } 33 | 34 | // ManagementUrl returns a URL with the given hostname in the format of `https:///edge/management/v1`. 35 | // The hostname provided may include a port. 36 | func ManagementUrl(hostname string) string { 37 | return concat(hostname, ManagementApiPath) 38 | } 39 | 40 | func concat(base, path string) string { 41 | if !strings.Contains(base, "://") { 42 | base = "https://" + base 43 | } 44 | if strings.HasSuffix(base, "/") { 45 | return strings.Trim(base, "/") + path 46 | } 47 | return base + path 48 | } 49 | -------------------------------------------------------------------------------- /edgexg/impl.go: -------------------------------------------------------------------------------- 1 | package edgexg 2 | 3 | import "github.com/openziti/sdk-golang/ziti/edge" 4 | 5 | const ( 6 | PayloadFlagsHeader uint8 = 0x10 7 | ) 8 | 9 | // headers to pass through fabric to the other side 10 | var HeadersToFabric = map[int32]uint8{ 11 | edge.FlagsHeader: PayloadFlagsHeader, 12 | } 13 | 14 | var HeadersFromFabric = map[uint8]int32{ 15 | PayloadFlagsHeader: edge.FlagsHeader, 16 | } 17 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # OpenZiti Go SDK Examples 2 | 3 | This folder contains examples showing how to use the OpenZiti Go SDK 4 | 5 | ## Building the SDK Examples 6 | 7 | ### Requirements 8 | * go 1.19 or later 9 | * gcc compiler 10 | 11 | ### Build 12 | Execute the following to build all examples. They will be placed in a folder in the example directory labeled `build` 13 | 1. CD to the example directory 14 | 15 | cd /example 16 | 1. Run the following to create the build directory and build the examples 17 | 18 | export ZITI_SDK_BUILD_DIR=$(pwd)/build 19 | mkdir $ZITI_SDK_BUILD_DIR 20 | go mod tidy 21 | go build -o build ./... 22 | 23 | ## SDK Examples Overview 24 | ### [chat](./chat) 25 | 26 | This demonstrates how to build network applications using the SDK with 27 | a CLI based chat server and client. 28 | 29 | ### [chat-p2p](./chat-p2p) 30 | 31 | This demonstrates how to build P2P network applications with a CLI based 32 | chat application which is modeled loosely on a VoIP. 33 | 34 | ### [curlz](./curlz) 35 | 36 | Shows how to integrate the SDK with the Go net/http library as a client. 37 | 38 | ### [simple-server](./simple-server) 39 | 40 | Shows how to integrate the SDK with the Go net/http library as a server. 41 | 42 | ### [grpc-example](./grpc-example) 43 | 44 | Shows how to integrate the SDK with GRPC as a client and server. 45 | 46 | ### [influxdb-client-go](./influxdb-client-go) 47 | 48 | Shows how to have the influxdb client work using the SDK. 49 | 50 | ### [reflect](./reflect) 51 | 52 | Basic echo client and server built with the SDK. 53 | 54 | ### [zcat](./zcat) 55 | 56 | Netcat like application which can work over OpenZiti. 57 | 58 | ### [zping](./zping) 59 | 60 | Client and server applications for measuring latency over an OpenZiti network. -------------------------------------------------------------------------------- /example/chat-p2p/setup.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | _ "embed" 21 | "github.com/openziti/runzmd" 22 | "github.com/openziti/runzmd/actionz" 23 | "github.com/spf13/cobra" 24 | "time" 25 | ) 26 | 27 | //go:embed setup.md 28 | var scriptSource []byte 29 | 30 | type setupAction struct { 31 | ControllerUrl string 32 | Username string 33 | Password string 34 | NewlinePause time.Duration 35 | AssumeDefault bool 36 | interactive bool 37 | } 38 | 39 | func (self *setupAction) GetControllerUrl() string { 40 | return self.ControllerUrl 41 | } 42 | 43 | func (self *setupAction) GetUsername() string { 44 | return self.Username 45 | } 46 | 47 | func (self *setupAction) GetPassword() string { 48 | return self.Password 49 | } 50 | 51 | func newSetupCmd() *cobra.Command { 52 | action := &setupAction{} 53 | 54 | cmd := &cobra.Command{ 55 | Use: "setup", 56 | Short: "Walks you through configuration for the sdk-golang chat-p2p example", 57 | Args: cobra.ExactArgs(0), 58 | RunE: action.run, 59 | } 60 | 61 | // allow interspersing positional args and flags 62 | cmd.Flags().SetInterspersed(true) 63 | cmd.Flags().StringVar(&action.ControllerUrl, "controller-url", "", "The Ziti controller URL to use") 64 | cmd.Flags().StringVarP(&action.Username, "username", "u", "", "The Ziti controller username to use") 65 | cmd.Flags().StringVarP(&action.Password, "password", "p", "", "The Ziti controller password to use") 66 | cmd.Flags().DurationVar(&action.NewlinePause, "newline-pause", time.Millisecond*10, "How long to pause between lines when scrolling") 67 | cmd.Flags().BoolVar(&action.interactive, "interactive", false, "Interactive mode, waiting for user input") 68 | 69 | return cmd 70 | } 71 | 72 | func (self *setupAction) run(*cobra.Command, []string) error { 73 | t := runzmd.NewRunner() 74 | t.NewLinePause = self.NewlinePause 75 | t.AssumeDefault = !self.interactive 76 | 77 | t.RegisterActionHandler("ziti", &actionz.ZitiRunnerAction{}) 78 | t.RegisterActionHandler("ziti-login", &actionz.ZitiEnsureLoggedIn{ 79 | LoginParams: self, 80 | }) 81 | t.RegisterActionHandler("keep-session-alive", &actionz.KeepSessionAliveAction{}) 82 | 83 | return t.Run(scriptSource) 84 | } 85 | -------------------------------------------------------------------------------- /example/chat-p2p/setup.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | This script sets up the services, policies and identities for the sdk-golang chat-p2p example. 4 | 5 | # Prerequisites 6 | 7 | You need at least one controller and an edge router running. for this to work. 8 | You can use the quick-start script found [here](https://github.com/openziti/ziti/tree/release-next/quickstart). 9 | 10 | # Setup 11 | 12 | ## Ensure we're logged into the controller 13 | 14 | ```action:ziti-login allowRetry=true 15 | ziti edge login 16 | ``` 17 | 18 | 19 | 20 | ## Remove any entities from previous runs 21 | 22 | ```action:ziti 23 | ziti edge delete service chat-p2p 24 | ziti edge delete identities user1 user2 user3 user4 25 | ziti edge delete service-policies chat-p2p-dial chat-p2p-bind 26 | ziti edge delete edge-router-policy chat-p2p 27 | ziti edge delete service-edge-router-policy chat-p2p 28 | ``` 29 | 30 | ## Create and enroll the client app identity 31 | 32 | ```action:ziti 33 | ziti edge create identity user user1 -a chat-p2p -o user1.jwt 34 | ziti edge enroll --rm user1.jwt 35 | 36 | ziti edge create identity user user2 -a chat-p2p -o user2.jwt 37 | ziti edge enroll --rm user2.jwt 38 | 39 | ziti edge create identity user user3 -a chat-p2p -o user3.jwt 40 | ziti edge enroll --rm user3.jwt 41 | 42 | ziti edge create identity user user4 -a chat-p2p -o user4.jwt 43 | ziti edge enroll --rm user4.jwt 44 | ``` 45 | 46 | ## Configure the dial and bind service policies 47 | 48 | ```action:ziti 49 | ziti edge create service-policy chat-p2p-dial Dial --service-roles '#chat-p2p' --identity-roles '#chat-p2p' 50 | ziti edge create service-policy chat-p2p-bind Bind --service-roles '#chat-p2p' --identity-roles '#chat-p2p' 51 | ``` 52 | 53 | ## Configure the edge router policy 54 | 55 | ```action:ziti 56 | ziti edge create edge-router-policy chat-p2p --edge-router-roles '#all' --identity-roles '#chat-p2p' 57 | ``` 58 | 59 | ## Configure the service edge router policy 60 | 61 | ```action:ziti 62 | ziti edge create service-edge-router-policy chat-p2p --edge-router-roles '#all' --service-roles '#chat-p2p' 63 | ``` 64 | 65 | ## Create the service 66 | 67 | ```action:ziti 68 | ziti edge create service chat-p2p -a chat-p2p 69 | ``` 70 | 71 | # Summary 72 | 73 | After you've configured the service side, you should now be to run the chat-p2p client for 74 | each of the four configured identities as follows. 75 | 76 | ``` 77 | chat-p2p -i user1.json 78 | chat-p2p -i user2.json 79 | chat-p2p -i user3.json 80 | chat-p2p -i user4.json 81 | ``` 82 | 83 | Note that you will need to run each chat-p2p command in a separate terminal. -------------------------------------------------------------------------------- /example/chat/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This example is a basic chat client showing how to embed zero trust connectivity into both server-side and client-side 3 | code. The server handles chat messages and broadcasts them to other clients on the chat. 4 | 5 | This example demonstrates: 6 | * Binding a service and listening for message events 7 | * Dialing a service and triggering message events 8 | 9 | # Requirements 10 | * an OpenZiti network. If you do not have one, you can use one of the [quickstarts](https://openziti.github.io/ziti/quickstarts/quickstart-overview.html) to set one up. 11 | * OpenZiti CLI to create services and identities on the OpenZiti Network 12 | 13 | ## Build the examples 14 | Refer to the [example README](../README.md) to build the SDK examples 15 | 16 | # Setup using the OpenZiti CLI 17 | These steps will configure the service using the OpenZiti CLI. At the end of these steps you will have created: 18 | * a service called `chat` 19 | * an identity to host (bind) the service 20 | * two identities to connect to (dial) the service 21 | * the service policies required to authorize the identities for bind and dial 22 | 23 | Steps: 24 | 1. Log into OpenZiti. The host:port and username/password will vary depending on your network. 25 | 26 | ziti edge login localhost:1280 -u admin -p admin 27 | 1. Run this script to create everything you need. 28 | 29 | echo Changing to build directory 30 | cd $ZITI_SDK_BUILD_DIR 31 | 32 | echo Create the service 33 | ziti edge create service chat --role-attributes chat-service 34 | 35 | echo Create three identities and enroll them 36 | ziti edge create identity user chevy -a chat.clients -o chevy.jwt 37 | ziti edge create identity user dan -a chat.clients -o dan.jwt 38 | ziti edge create identity device chat.server -a chat.servers -o chat.server.jwt 39 | ziti edge enroll --jwt chat.server.jwt 40 | ziti edge enroll --jwt chevy.jwt 41 | ziti edge enroll --jwt dan.jwt 42 | 43 | echo Create service policies 44 | ziti edge create service-policy chat.dial Dial --identity-roles '#chat.clients' --service-roles '#chat-service' 45 | ziti edge create service-policy chat.bind Bind --identity-roles '#chat.servers' --service-roles '#chat-service' 46 | 47 | echo Run policy advisor to check 48 | ziti edge policy-advisor services 49 | 1. Run the server. 50 | 51 | ./chat-server chat.server.json 52 | 1. Run a client 53 | 54 | ./chat-client chevy chevy.json 55 | 1. Run another client 56 | 57 | ./chat-client dan dan.json 58 | ## Example output 59 | The following is the output you will see from the server and client side after running the previous commands. 60 | **Server** 61 | ``` 62 | $ ./chat-server chat.server.json 63 | INFO[0000] binding service chat 64 | INFO[0014] new connection 65 | INFO[0014] client 'chevy' connected 66 | INFO[0038] new connection 67 | INFO[0038] client 'dan' connected 68 | ``` 69 | **Client 1 (Chevy)** 70 | ``` 71 | $ ./chat-client chevy chevy.json 72 | doctor 73 | dan: doctor 74 | ``` 75 | **Client 2 (Dan)** 76 | ``` 77 | $ ./chat-client dan dan.json 78 | chevy: doctor 79 | doctor 80 | ``` 81 | # Teardown 82 | Done with the example? This script will remove everything created during setup. 83 | ``` 84 | ziti edge login localhost:1280 -u admin -p admin 85 | 86 | echo Removing service policies 87 | ziti edge delete service-policy chat.dial 88 | ziti edge delete service-policy chat.bind 89 | 90 | echo Removing identities 91 | ziti edge delete identity chevy 92 | ziti edge delete identity dan 93 | ziti edge delete identity chat.server 94 | 95 | echo Removing service 96 | ziti edge delete service chat 97 | ``` 98 | -------------------------------------------------------------------------------- /example/chat/chat-client/chat-client.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/openziti/sdk-golang/ziti" 22 | "io" 23 | "os" 24 | ) 25 | 26 | func main() { 27 | if len(os.Args) < 3 { 28 | fmt.Printf("Insufficient arguments provided\n\nUsage: ./chat-client \n\n") 29 | return 30 | } 31 | name := os.Args[1] 32 | 33 | // Get identity config 34 | cfg, err := ziti.NewConfigFromFile(os.Args[2]) 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | // Get service name (defaults to "chat") 40 | serviceName := "chat" 41 | if len(os.Args) > 3 { 42 | serviceName = os.Args[3] 43 | } 44 | 45 | context, err := ziti.NewContext(cfg) 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | conn, err := context.Dial(serviceName) 52 | if err != nil { 53 | fmt.Printf("failed to dial service %v, err: %+v\n", serviceName, err) 54 | panic(err) 55 | } 56 | 57 | if _, err := conn.Write([]byte(name)); err != nil { 58 | panic(err) 59 | } 60 | 61 | go func() { 62 | written, err := io.Copy(conn, os.Stdin) 63 | fmt.Printf("finished writing (stdin => conn) %v. err? %v\n", written, err) 64 | }() 65 | 66 | written, err := io.Copy(os.Stdout, conn) 67 | fmt.Printf("finished writing (conn => stdout) %v. err? %v\n", written, err) 68 | } 69 | -------------------------------------------------------------------------------- /example/chat/chat-server/chat-server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/michaelquigley/pfxlog" 22 | "github.com/openziti/sdk-golang/ziti" 23 | "github.com/sirupsen/logrus" 24 | "net" 25 | "os" 26 | "time" 27 | ) 28 | 29 | type chatServer struct { 30 | clients map[string]net.Conn 31 | eventC chan event 32 | } 33 | 34 | func (server *chatServer) run() { 35 | for event := range server.eventC { 36 | event.handle(server) 37 | } 38 | } 39 | 40 | func (server *chatServer) handleChat(conn net.Conn) { 41 | buf := make([]byte, 1024) 42 | n, err := conn.Read(buf) 43 | if err != nil { 44 | _ = conn.Close() 45 | return 46 | } 47 | name := string(buf[:n]) 48 | server.eventC <- &clientConnectedEvent{ 49 | name: name, 50 | conn: conn, 51 | } 52 | 53 | for { 54 | buf := make([]byte, 1024) 55 | n, err := conn.Read(buf) 56 | if err != nil { 57 | _ = conn.Close() 58 | server.eventC <- &clientDisconnectEvent{name: name} 59 | return 60 | } 61 | msg := string(buf[:n]) 62 | server.eventC <- &msgEvent{ 63 | source: name, 64 | msg: msg, 65 | } 66 | } 67 | } 68 | 69 | type event interface { 70 | handle(server *chatServer) 71 | } 72 | 73 | type clientConnectedEvent struct { 74 | name string 75 | conn net.Conn 76 | } 77 | 78 | func (event *clientConnectedEvent) handle(server *chatServer) { 79 | pfxlog.Logger().Infof("client '%v' connected\n", event.name) 80 | server.clients[event.name] = event.conn 81 | } 82 | 83 | type clientDisconnectEvent struct { 84 | name string 85 | } 86 | 87 | func (event *clientDisconnectEvent) handle(server *chatServer) { 88 | pfxlog.Logger().Infof("client '%v' disconnected\n", event.name) 89 | delete(server.clients, event.name) 90 | } 91 | 92 | type msgEvent struct { 93 | source string 94 | msg string 95 | } 96 | 97 | func (event *msgEvent) handle(server *chatServer) { 98 | msg := []byte(fmt.Sprintf("%v: %v", event.source, event.msg)) 99 | pfxlog.Logger().Debug(string(msg)) 100 | for name, conn := range server.clients { 101 | if name != event.source { 102 | if _, err := conn.Write(msg); err != nil { 103 | pfxlog.Logger().Errorf("failed to write to %v (%v). closing connection", name, err) 104 | delete(server.clients, name) 105 | _ = conn.Close() 106 | } 107 | } 108 | } 109 | } 110 | 111 | func main() { 112 | if os.Getenv("DEBUG") == "true" { 113 | pfxlog.GlobalInit(logrus.DebugLevel, pfxlog.DefaultOptions()) 114 | pfxlog.Logger().Debugf("debug enabled") 115 | } 116 | 117 | logger := pfxlog.Logger() 118 | 119 | if len(os.Args) < 2 { 120 | fmt.Printf("Insufficient arguments provided\n\nUsage: ./chat-server \n\n") 121 | return 122 | } 123 | 124 | // Get identity config 125 | cfg, err := ziti.NewConfigFromFile(os.Args[1]) 126 | if err != nil { 127 | panic(err) 128 | } 129 | 130 | // Get service name (defaults to "chat") 131 | serviceName := "chat" 132 | if len(os.Args) > 2 { 133 | serviceName = os.Args[2] 134 | } 135 | 136 | options := ziti.ListenOptions{ 137 | ConnectTimeout: 5 * time.Minute, 138 | MaxConnections: 3, 139 | } 140 | logger.Infof("binding service %v\n", serviceName) 141 | ctx, err := ziti.NewContext(cfg) 142 | 143 | if err != nil { 144 | panic(err) 145 | } 146 | 147 | listener, err := ctx.ListenWithOptions(serviceName, &options) 148 | if err != nil { 149 | logrus.Errorf("Error binding service %+v", err) 150 | panic(err) 151 | } 152 | 153 | server := &chatServer{ 154 | clients: map[string]net.Conn{}, 155 | eventC: make(chan event, 10), 156 | } 157 | go server.run() 158 | 159 | for { 160 | conn, err := listener.Accept() 161 | if err != nil { 162 | logger.Errorf("server error, exiting: %+v\n", err) 163 | panic(err) 164 | } 165 | logger.Infof("new connection") 166 | go server.handleChat(conn) 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /example/curlz/curlz.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "github.com/openziti/sdk-golang/ziti" 23 | "io" 24 | "net" 25 | "net/http" 26 | "os" 27 | "strings" 28 | ) 29 | 30 | type ZitiDialContext struct { 31 | context ziti.Context 32 | } 33 | 34 | func (dc *ZitiDialContext) Dial(_ context.Context, _ string, addr string) (net.Conn, error) { 35 | service := strings.Split(addr, ":")[0] // will always get passed host:port 36 | return dc.context.Dial(service) 37 | } 38 | 39 | func newZitiClient() *http.Client { 40 | // Get identity config 41 | cfg, err := ziti.NewConfigFromFile(os.Args[2]) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | ctx, err := ziti.NewContext(cfg) 47 | 48 | if err != nil { 49 | panic(err) 50 | } 51 | 52 | zitiDialContext := ZitiDialContext{context: ctx} 53 | 54 | zitiTransport := http.DefaultTransport.(*http.Transport).Clone() // copy default transport 55 | zitiTransport.DialContext = zitiDialContext.Dial 56 | zitiTransport.TLSClientConfig.InsecureSkipVerify = true 57 | return &http.Client{Transport: zitiTransport} 58 | } 59 | 60 | func main() { 61 | if len(os.Args) < 3 { 62 | fmt.Printf("Insufficient arguments provided\n\nUsage: ./curlz \n\n") 63 | return 64 | } 65 | 66 | resp, err := newZitiClient().Get(os.Args[1]) 67 | if err != nil { 68 | panic(err) 69 | } 70 | 71 | _, err = io.Copy(os.Stdout, resp.Body) 72 | if err != nil { 73 | panic(err) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/curlz/unzitified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/ad526d40ec0833d7fc589790e97243b6131c4bf8/example/curlz/unzitified.png -------------------------------------------------------------------------------- /example/curlz/zitified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/ad526d40ec0833d7fc589790e97243b6131c4bf8/example/curlz/zitified.png -------------------------------------------------------------------------------- /example/grpc-example/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This sample is based on [gRPC Hello World](https://github.com/grpc/grpc-go/tree/master/examples). 3 | It demonstrates how convert existing gRPC application to communicate over an app embedded zero trust 4 | OpenZiti Network 5 | 6 | This example demonstrates: 7 | * Binding a service and listening for service calls 8 | * Dialing a service and triggering service calls 9 | 10 | ## Requirements 11 | * an OpenZiti network. If you do not have one, you can use one of the [quickstarts](https://openziti.github.io/ziti/quickstarts/quickstart-overview.html) to set one up. 12 | * OpenZiti CLI to create services and identities on the OpenZiti Network 13 | 14 | ## Build the examples 15 | Refer to the [example README](../README.md) to build the SDK examples 16 | 17 | ## Setup using the OpenZiti CLI 18 | These steps will configure the service using the OpenZiti CLI. At the end of these steps you will have created: 19 | * a service called `grpc` 20 | * an identity to host (bind) the service 21 | * an identity to connect to (dial) the service 22 | * the service policies required to authorize the identities for bind and dial 23 | 24 | Steps: 25 | 1. Log into OpenZiti. The host:port and username/password will vary depending on your network. 26 | 27 | ziti edge login localhost:1280 -u admin -p admin 28 | 1. Run this script to create everything you need. 29 | 30 | echo Changing to build directory 31 | cd $ZITI_SDK_BUILD_DIR 32 | 33 | echo Create the service 34 | ziti edge create service grpc --role-attributes grpc-service 35 | 36 | echo Create three identities and enroll them 37 | ziti edge create identity device grpc.client -a grpc.clients -o grpc.client.jwt 38 | ziti edge create identity device grpc.server -a grpc.servers -o grpc.server.jwt 39 | ziti edge enroll --jwt grpc.server.jwt 40 | ziti edge enroll --jwt grpc.client.jwt 41 | 42 | echo Create service policies 43 | ziti edge create service-policy grpc.dial Dial --identity-roles '#grpc.clients' --service-roles '#grpc-service' 44 | ziti edge create service-policy grpc.bind Bind --identity-roles '#grpc.servers' --service-roles '#grpc-service' 45 | 46 | echo Run policy advisor to check 47 | ziti edge policy-advisor services 48 | 1. Run the server. 49 | 50 | ./grpc-server --identity grpc.server.json --service grpc 51 | 1. Run the client 52 | 53 | ./grpc-client --identity grpc.client.json --service grpc --name World 54 | ### Example output 55 | The following is the output you'll see from the server and client side after running the previous commands. 56 | **Server** 57 | ``` 58 | $ ./grpc-server --identity grpc.server.json --service grpc 59 | 2022/10/21 11:17:34 server listening at grpc 60 | 2022/10/21 11:18:09 Received: World 61 | ``` 62 | **Client** 63 | ``` 64 | $ ./grpc-client --identity grpc.client.json --service grpc --name World 65 | 2022/10/21 13:26:19 Greeting: Hello World 66 | ``` 67 | ## Teardown 68 | Done with the example? This script will remove everything created during setup. 69 | ``` 70 | ziti edge login localhost:1280 -u admin -p admin 71 | 72 | echo Removing service policies 73 | ziti edge delete service-policy grpc.dial 74 | ziti edge delete service-policy grpc.bind 75 | 76 | echo Removing identities 77 | ziti edge delete identity grpc.client 78 | ziti edge delete identity grpc.server 79 | 80 | echo Removing service 81 | ziti edge delete service grpc 82 | ``` 83 | -------------------------------------------------------------------------------- /example/grpc-example/grpc-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/openziti/sdk-golang/ziti" 7 | "log" 8 | "net" 9 | "time" 10 | 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/credentials/insecure" 13 | pb "google.golang.org/grpc/examples/helloworld/helloworld" 14 | ) 15 | 16 | const ( 17 | defaultName = "world" 18 | ) 19 | 20 | var ( 21 | name = flag.String("name", defaultName, "Name to greet") 22 | 23 | identity = flag.String("identity", "", "Ziti Identity file") 24 | service = flag.String("service", "", "Ziti Service") 25 | ) 26 | 27 | func main() { 28 | flag.Parse() 29 | cfg, err := ziti.NewConfigFromFile(*identity) 30 | if err != nil { 31 | log.Fatalf("failed to load config err=%v", err) 32 | } 33 | 34 | ztx, err := ziti.NewContext(cfg) 35 | 36 | if err != nil { 37 | panic(err) 38 | } 39 | 40 | err = ztx.Authenticate() 41 | if err != nil { 42 | log.Fatalf("failed to authenticate: %v", err) 43 | } 44 | // Set up a connection to the server. 45 | conn, err := grpc.Dial(*service, 46 | grpc.WithTransportCredentials(insecure.NewCredentials()), 47 | grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { 48 | return ztx.Dial(s) 49 | }), 50 | ) 51 | if err != nil { 52 | log.Fatalf("did not connect: %v", err) 53 | } 54 | defer conn.Close() 55 | c := pb.NewGreeterClient(conn) 56 | 57 | // Contact the server and print out its response. 58 | ctx, cancel := context.WithTimeout(context.Background(), time.Second) 59 | defer cancel() 60 | r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name}) 61 | if err != nil { 62 | log.Fatalf("could not greet: %v", err) 63 | } 64 | log.Printf("Greeting: %s", r.GetMessage()) 65 | } 66 | -------------------------------------------------------------------------------- /example/grpc-example/grpc-server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/openziti/sdk-golang/ziti" 7 | "google.golang.org/grpc" 8 | pb "google.golang.org/grpc/examples/helloworld/helloworld" 9 | "log" 10 | ) 11 | 12 | var ( 13 | identity = flag.String("identity", "", "Ziti Identity file") 14 | service = flag.String("service", "", "Ziti Service") 15 | ) 16 | 17 | // server is used to implement helloworld.GreeterServer. 18 | type server struct { 19 | pb.UnimplementedGreeterServer 20 | } 21 | 22 | // SayHello implements helloworld.GreeterServer 23 | func (s *server) SayHello(_ context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 24 | log.Printf("Received: %v", in.GetName()) 25 | return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil 26 | } 27 | 28 | func main() { 29 | flag.Parse() 30 | cfg, err := ziti.NewConfigFromFile(*identity) 31 | if err != nil { 32 | log.Fatalf("failed to load ziti identity{%v}: %v", identity, err) 33 | } 34 | 35 | ztx, err := ziti.NewContext(cfg) 36 | 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | err = ztx.Authenticate() 42 | if err != nil { 43 | log.Fatalf("failed to authenticate: %v", err) 44 | } 45 | 46 | lis, err := ztx.Listen(*service) 47 | if err != nil { 48 | log.Fatalf("failed to listen: %v", err) 49 | } 50 | s := grpc.NewServer() 51 | pb.RegisterGreeterServer(s, &server{}) 52 | log.Printf("server listening at %v", lis.Addr()) 53 | if err := s.Serve(lis); err != nil { 54 | log.Fatalf("failed to serve: %v", err) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/http-client/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | This example demonstrates a zitified HTTP client. 4 | 5 | This example demonstrates: 6 | 7 | * Dialing a service by intercept address. 8 | 9 | ## Requirements 10 | 11 | * an OpenZiti network. If you do not have one, you can use one of the [quickstarts](https://openziti.github.io/ziti/quickstarts/quickstart-overview.html) to set one up. 12 | * OpenZiti CLI to create services and identities on the OpenZiti Network 13 | 14 | ## Build the example 15 | 16 | Refer to the [example README](../README.md) to build the SDK examples 17 | 18 | ## Part 1: Set up a cURLz to a non-zitified endpoint 19 | 20 | These steps will configure the service using the OpenZiti CLI. In this example, the traffic starts on the overlay zero 21 | trust network and then is offloaded onto the underlay network. 22 | 23 | ### Part 1 Architecture Overview 24 | 25 | ![image](unzitified.png) 26 | 27 | At the end of these steps you will have created: 28 | 29 | * a service called `web.endpoint` 30 | * an identity to connect to (dial) the service 31 | * the service config to connect the service to the overlay 32 | * the service policies required to authorize the identities for bind and dial 33 | 34 | Steps: 35 | 36 | 1. log into Ziti. The host:port and username/password will vary depending on your network. 37 | 38 | ```bash 39 | ziti edge login localhost:1280 -u admin -p admin 40 | ``` 41 | 42 | 1. Determine your edge router's name and populate this environment variable with it. 43 | 44 | ```bash 45 | ziti edge list edge-routers 46 | export ZITI_EDGE_ROUTER= 47 | ``` 48 | 49 | 1. Run this script to create everything you need. 50 | 51 | ```bash 52 | cd /example/build 53 | 54 | echo Create the service config 55 | ziti edge create config httpbin.hostv1 host.v1 '{"protocol":"tcp", "address":"httpbin.org","port":80}' 56 | ziti edge create config httpbin.clientv1 intercept.v1 '{"protocols":["tcp"], "addresses":["httpbin.ziti"],"portRanges":[{"low":80,"high":80}]}' 57 | 58 | echo Create the service 59 | ziti edge create service ziti.httpbin --configs "httpbin.hostv1,httpbin.clientv1" 60 | 61 | echo Create an identity to make the dial request and enroll it 62 | ziti edge create identity user http-client -a clients -o http-client.jwt 63 | ziti edge enroll --jwt http-client.jwt 64 | 65 | echo Create service policies 66 | ziti edge create service-policy ziti.httpbin.dial Dial --service-roles "@ziti.httpbin" --identity-roles "#clients" 67 | ziti edge create service-policy ziti.httpbin.bind Bind --service-roles "@ziti.httpbin" --identity-roles "@${ZITI_EDGE_ROUTER}" 68 | 69 | echo Create edge router policies 70 | ziti edge create edge-router-policy ziti.httpbin-edge-router-policy --edge-router-roles '#all' --identity-roles '#clients,#servers' 71 | ziti edge create service-edge-router-policy ziti.httpbin-service-edge-router-policy --edge-router-roles '#all' --service-roles '@ziti.httpbin' 72 | 73 | echo Run policy advisor to check 74 | ziti edge policy-advisor services 75 | ``` 76 | 77 | 1. Run the `http-client` example for service `ziti.httpbin` using intercept address `tcp:httpbin.ziti:80` 78 | 79 | ```bash 80 | ZITI_IDENTITIES=http-client.json ./http-client http://httpbin.ziti 81 | ``` 82 | 83 | ### Example Output 84 | 85 | The following is the output you'll see. 86 | 87 | ```bash 88 | export ZITI_IDENTITIES=http-client.json 89 | $ ./http-client http://httpbin.ziti/json 90 | { 91 | "slideshow": { 92 | "author": "Yours Truly", 93 | "date": "date of publication", 94 | "slides": [ 95 | { 96 | "title": "Wake up to WonderWidgets!", 97 | "type": "all" 98 | }, 99 | { 100 | "items": [ 101 | "Why WonderWidgets are great", 102 | "Who buys WonderWidgets" 103 | ], 104 | "title": "Overview", 105 | "type": "all" 106 | } 107 | ], 108 | "title": "Sample Slide Show" 109 | } 110 | } 111 | ``` 112 | -------------------------------------------------------------------------------- /example/http-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/openziti/sdk-golang/ziti" 6 | "io" 7 | "net" 8 | "net/http" 9 | "os" 10 | ) 11 | 12 | func newZitiClient() *http.Client { 13 | ziti.DefaultCollection.ForAll(func(ctx ziti.Context) { 14 | ctx.Authenticate() 15 | }) 16 | zitiTransport := http.DefaultTransport.(*http.Transport).Clone() // copy default transport 17 | zitiTransport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { 18 | dialer := ziti.DefaultCollection.NewDialer() 19 | return dialer.Dial(network, addr) 20 | } 21 | zitiTransport.TLSClientConfig.InsecureSkipVerify = true 22 | return &http.Client{Transport: zitiTransport} 23 | } 24 | 25 | // this is a clone of ../curlz but showing the use of ziti.Dialer 26 | // identities are loaded from ZITI_IDENTITIES environment variable -- ';'-separated list of identity files 27 | // 28 | // saple usage: 29 | // ``` 30 | // 31 | // $ export ZITI_IDENTITIES= 32 | // $ http-client http:///path 33 | // 34 | // ``` 35 | func main() { 36 | resp, err := newZitiClient().Get(os.Args[1]) 37 | if err != nil { 38 | panic(err) 39 | } 40 | 41 | _, err = io.Copy(os.Stdout, resp.Body) 42 | if err != nil { 43 | panic(err) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /example/influxdb-client-go/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | influxdb2 "github.com/influxdata/influxdb-client-go/v2" 8 | "github.com/openziti/sdk-golang/ziti" 9 | "github.com/sirupsen/logrus" 10 | "net" 11 | "net/http" 12 | "time" 13 | ) 14 | 15 | var svcName = "httpsdk" 16 | 17 | type ZitiDoer struct { 18 | httpClient *http.Client 19 | } 20 | type ZitiDialContext struct { 21 | context ziti.Context 22 | serviceName string 23 | } 24 | 25 | func (dc *ZitiDialContext) Dial(_ context.Context, _ string, _ string) (net.Conn, error) { 26 | return dc.context.Dial(dc.serviceName) 27 | } 28 | func NewZitiDoer(cfgFile string) *ZitiDoer { 29 | zitiCfg, err := ziti.NewConfigFromFile(cfgFile) 30 | if err != nil { 31 | logrus.Errorf("failed to load ziti configuration file: %v", err) 32 | } 33 | ctx, err := ziti.NewContext(zitiCfg) 34 | 35 | if err != nil { 36 | panic(err) 37 | } 38 | 39 | zitiDialContext := ZitiDialContext{context: ctx, serviceName: svcName} 40 | zitiTransport := http.DefaultTransport.(*http.Transport).Clone() // copy default transport 41 | zitiTransport.DialContext = zitiDialContext.Dial 42 | doer := &ZitiDoer{} 43 | doer.httpClient = &http.Client{ 44 | Transport: zitiTransport, 45 | } 46 | return doer 47 | } 48 | func (doer *ZitiDoer) Do(httpReq *http.Request) (*http.Response, error) { 49 | return doer.httpClient.Do(httpReq) 50 | } 51 | 52 | func main() { 53 | userName := "admin" 54 | password := "admin" 55 | dbPtr := "test" 56 | identityFile := `influxdb-client-go-test.json` 57 | flag.Parse() 58 | 59 | //create a new "Doer" - in this case it is a simple struct which implements "Do" 60 | zitiDoer := NewZitiDoer(identityFile) 61 | 62 | token := fmt.Sprintf("%s:%s", userName, password) 63 | // Create a new client using an InfluxDB server base URL and an authentication token 64 | // For authentication token supply a string in the form: "username:password" as a token. Set empty value for an unauthenticated server 65 | opts := influxdb2.DefaultOptions() 66 | opts.HTTPOptions().SetHTTPDoer(zitiDoer) 67 | client := influxdb2.NewClientWithOptions("http://influx-no-ssl:8086", token, opts) 68 | 69 | // Get the blocking write client 70 | // Supply a string in the form database/retention-policy as a bucket. Skip retention policy for the default one, use just a database name (without the slash character) 71 | // Org name is not used 72 | bucket := dbPtr + "/autogen" 73 | writeAPI := client.WriteAPIBlocking("", bucket) 74 | // create point using full params constructor 75 | p := influxdb2.NewPoint("stat", 76 | map[string]string{"unit": "temperature"}, 77 | map[string]interface{}{"avg": 24.5, "max": 45}, 78 | time.Now()) 79 | // Write data 80 | err := writeAPI.WritePoint(context.Background(), p) 81 | if err != nil { 82 | fmt.Printf("Write error: %s\n", err.Error()) 83 | } 84 | 85 | // Get query client. Org name is not used 86 | queryAPI := client.QueryAPI("") 87 | // Supply string in a form database/retention-policy as a bucket. Skip retention policy for the default one, use just a database name (without the slash character) 88 | result, err := queryAPI.Query(context.Background(), `from(bucket:"`+bucket+`")|> range(start: -1h) |> filter(fn: (r) => r._measurement == "stat")`) 89 | if err == nil { 90 | for result.Next() { 91 | if result.TableChanged() { 92 | fmt.Printf("table: %s\n", result.TableMetadata().String()) 93 | } 94 | fmt.Printf("row: %s\n", result.Record().String()) 95 | } 96 | if result.Err() != nil { 97 | fmt.Printf("Query error: %s\n", result.Err().Error()) 98 | } 99 | } else { 100 | fmt.Printf("Query error: %s\n", err.Error()) 101 | } 102 | 103 | // Close client 104 | client.Close() 105 | } 106 | -------------------------------------------------------------------------------- /example/jwtchat/README.md: -------------------------------------------------------------------------------- 1 | # jwtchat 2 | 3 | A set of three binaries used to demonstrate the OpenZiti GoLang SDK using external JWT signers to athenticate. 4 | 5 | # Binaries 6 | 7 | ## jwtchat-idp 8 | 9 | Stands up an OIDC compliant OpenId Provider (OP) that allows all OIDC flows. This example uses Client Credentials. 10 | 11 | It is run without any arguments and host the OPIDC API on `localhost:9998` 12 | 13 | ## jwtchat-client 14 | 15 | Attempts to contact a controller listening on `localhost:1280` and an OIDC compliant provider on `localhost:9998`. 16 | 17 | It is run without any arguments and does not open any ports. It attempts to connection/dial a service named `jwtchat` 18 | 19 | It will attempt to authenticate with the OIDC provider as: 20 | 21 | - username: `cid1` 22 | - password: `cid1secreat` 23 | 24 | 25 | ## jwtchat-server 26 | 27 | Attempts to contact a controller listening on `localhost:1280` and an OIDC compliant provider on `localhost:9998`. 28 | 29 | It is run without any arguments and does not open any ports. It attempts to host/bind a service named `jwtchat` 30 | 31 | It will attempt to authenticate with the OIDC provider as: 32 | 33 | - username: `cid2` 34 | - password: `cid2secreat` 35 | 36 | # Setup 37 | 38 | *Note: For Powershell ensure you escape pound (#) symbols with a grave tick (`)* 39 | 40 | 1) Stand up an OpenZiti network 41 | 2) Add an External JWT Signer with a JWKS endpoint 42 | 1) `ziti edge create ext-jwt-signer jwtchat-idp "http://localhost:9998" -a openziti -u "http://localhost:9998/keys"` 43 | 2) Save the resulting `ext-jwt-signer` 44 | 3) Create an authentication policy that allows the new `ext-jwt-signer` to authenticate identities 45 | 1) `ziti edge create auth-policy jwtchat --primary-ext-jwt-allowed --primary-ext-jwt-allowed-signers ` 46 | 2) Save the resulting `auth-policy` id 47 | 4) Create two identities (client, server) 48 | 1) `ziti edge create identity service cid1 --external-id cid1 -a jwtchat -P ` 49 | 2) `ziti edge create identity service cid2 --external-id cid2 -a jwtchat -P ` 50 | 5) Create at least one Edge Router 51 | 1) `ziti edge create edge-router myRouter -o myRouter.jwt` 52 | 2) `ziti router enroll -j myRouter.jwt` 53 | 6) Create a service named `jwtchat` with attribute `jwtchat` 54 | 1) `ziti edge create service jwtchat -a jwtchat` 55 | 7) Creat an Edge Router Policy that gives the new identities access to your Edge Routers 56 | 1) `ziti edge create edge-router-policy jwtchat --identity-roles #jwtchat --edge-router-roles #all` 57 | 8) Create a Service Edge Router Policy that allows `jwtchat` service usage on your Edge Routers 58 | 1) `ziti edge create service-edge-router-policy jwtchat --service-roles #jwtchat --edge-router-roles #all` 59 | 9) Create a Service Policy that allows your identities access to the `jwtchat` service 60 | 1) `ziti edge create service-policy jwtchatDial Dial --service-roles #jwtchat --identity-roles #jwtchat` 61 | 2) `ziti edge create service-policy jwtchatBind Bind --service-roles #jwtchat --identity-roles #jwtchat` 62 | 10) Start the `jwtchat-idp` process 63 | 11) Start the `jwtchat-server` process 64 | 12) Start the `jwtchat-client` process 65 | -------------------------------------------------------------------------------- /example/jwtchat/jwtchat-client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "github.com/Jeffail/gabs" 8 | edge_apis "github.com/openziti/sdk-golang/edge-apis" 9 | "github.com/openziti/sdk-golang/ziti" 10 | "github.com/pkg/errors" 11 | log "github.com/sirupsen/logrus" 12 | "gopkg.in/resty.v1" 13 | "os" 14 | "os/signal" 15 | ) 16 | 17 | func main() { 18 | openzitiURL := flag.String("openziti-url", "https://localhost:1280", "URL of the OpenZiti service") 19 | idpTokenUrl := flag.String("idp-token-url", "http://localhost:9998/oauth/token", "URL of the Identity Provider") 20 | fmt.Printf("hi there\n\n") 21 | fmt.Println(*idpTokenUrl) 22 | fmt.Printf("hi there\n\n") 23 | clientID := flag.String("client-id", "cid2", "Client ID for authentication") 24 | clientSecret := flag.String("client-secret", "cid2secret", "Client Secret for authentication") 25 | grantType := flag.String("grant-type", "client_credentials", "The grant type to use") 26 | scope := flag.String("scope", "openid", "The scope to use") 27 | 28 | // Parse flags 29 | flag.Parse() 30 | 31 | // Print values 32 | fmt.Println("OpenZiti URL\t:", *openzitiURL) 33 | fmt.Println("IDP URL\t\t:", *idpTokenUrl) 34 | fmt.Println("Client ID\t:", *clientID) 35 | fmt.Println("Client Secret\t:", *clientSecret) 36 | fmt.Println("Grant Type\t:", *grantType) 37 | fmt.Println("Scope\t\t:", *scope) 38 | 39 | c := make(chan os.Signal, 1) 40 | signal.Notify(c, os.Interrupt) 41 | 42 | jwtToken, err := getExternalJWT(*clientID, *clientSecret, *grantType, *scope, *idpTokenUrl) 43 | 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | caPool, err := ziti.GetControllerWellKnownCaPool(*openzitiURL) 49 | 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | credentials := edge_apis.NewJwtCredentials(jwtToken) 55 | credentials.CaPool = caPool 56 | 57 | cfg := &ziti.Config{ 58 | ZtAPI: *openzitiURL + "/edge/client/v1", 59 | Credentials: credentials, 60 | } 61 | ctx, err := ziti.NewContext(cfg) 62 | 63 | if err != nil { 64 | panic(err) 65 | } 66 | 67 | err = ctx.Authenticate() 68 | 69 | if err != nil { 70 | panic(err) 71 | } 72 | 73 | svcs, err := ctx.GetServices() 74 | 75 | if err != nil { 76 | panic(err) 77 | } 78 | 79 | found := false 80 | for _, svc := range svcs { 81 | if *svc.Name == "jwtchat" { 82 | found = true 83 | break 84 | } 85 | } 86 | if !found { 87 | panic("jwtchat service not found") 88 | } 89 | 90 | conn, err := ctx.Dial("jwtchat") 91 | 92 | if err != nil { 93 | panic(err) 94 | } 95 | 96 | log.Println("listening for service: jwtchat") 97 | 98 | defer func() { 99 | _ = conn.Close() 100 | }() 101 | 102 | go func() { 103 | byteBuffer := make([]byte, 128) 104 | 105 | for { 106 | n, err := conn.Read(byteBuffer) 107 | 108 | if err != nil { 109 | log.Errorf("error reading, exiting: %s", err) 110 | return 111 | } 112 | 113 | if n != 0 { 114 | fmt.Printf("server: %s", string(byteBuffer[0:n])) 115 | } 116 | } 117 | }() 118 | 119 | go func() { 120 | reader := bufio.NewReader(os.Stdin) 121 | for { 122 | fmt.Print("-> ") 123 | text, _ := reader.ReadString('\n') 124 | 125 | _, err := conn.Write([]byte(text)) 126 | 127 | if err != nil { 128 | log.Errorf("error writing, exiting: %s", err) 129 | return 130 | } 131 | } 132 | }() 133 | 134 | <-c 135 | 136 | return 137 | } 138 | 139 | // getExternalJWT will use Open ID Connect's client credentials flow to obtain a JWT from the jwtchat-idp executable. 140 | func getExternalJWT(clientId string, clientSecret string, grantType string, scope string, idpTokenUrl string) (string, error) { 141 | resp, err := resty.R().SetFormData(map[string]string{ 142 | "client_secret": clientSecret, 143 | "client_id": clientId, 144 | "grant_type": grantType, 145 | "scope": scope, 146 | }).Post(idpTokenUrl) 147 | 148 | if err != nil { 149 | return "", err 150 | } 151 | json := resp.Body() 152 | jsonContainer, err := gabs.ParseJSON(json) 153 | 154 | if err != nil { 155 | return "", err 156 | } 157 | 158 | tokenName := "access_token" 159 | if !jsonContainer.ExistsP(tokenName) { 160 | return "", errors.New("no " + tokenName + " property found") 161 | } 162 | 163 | token, ok := jsonContainer.Path(tokenName).Data().(string) 164 | if !ok { 165 | return "", errors.New(tokenName + " was not a valid JSON string") 166 | } 167 | 168 | return token, nil 169 | } 170 | -------------------------------------------------------------------------------- /example/jwtchat/jwtchat-idp/exampleop/login.go: -------------------------------------------------------------------------------- 1 | package exampleop 2 | 3 | import ( 4 | "fmt" 5 | "html/template" 6 | "net/http" 7 | 8 | "github.com/gorilla/mux" 9 | ) 10 | 11 | const ( 12 | queryAuthRequestID = "authRequestID" 13 | ) 14 | 15 | var loginTmpl, _ = template.New("login").Parse(` 16 | 17 | 18 | 19 | 20 | Login 21 | 22 | 23 |
24 | 25 | 26 | 27 |
28 | 29 | 30 |
31 | 32 |
33 | 34 | 35 |
36 | 37 |

{{.Error}}

38 | 39 | 40 |
41 | 42 | `) 43 | 44 | type login struct { 45 | authenticate authenticate 46 | router *mux.Router 47 | callback func(string) string 48 | } 49 | 50 | func NewLogin(authenticate authenticate, callback func(string) string) *login { 51 | l := &login{ 52 | authenticate: authenticate, 53 | callback: callback, 54 | } 55 | l.createRouter() 56 | return l 57 | } 58 | 59 | func (l *login) createRouter() { 60 | l.router = mux.NewRouter() 61 | l.router.Path("/username").Methods("GET").HandlerFunc(l.loginHandler) 62 | l.router.Path("/username").Methods("POST").HandlerFunc(l.checkLoginHandler) 63 | } 64 | 65 | type authenticate interface { 66 | CheckUsernamePassword(username, password, id string) (*interface{}, error) 67 | } 68 | 69 | func (l *login) loginHandler(w http.ResponseWriter, r *http.Request) { 70 | err := r.ParseForm() 71 | if err != nil { 72 | http.Error(w, fmt.Sprintf("cannot parse form:%s", err), http.StatusInternalServerError) 73 | return 74 | } 75 | // the oidc package will pass the id of the auth request as query parameter 76 | // we will use this id through the login process and therefore pass it to the login page 77 | renderLogin(w, r.FormValue(queryAuthRequestID), nil) 78 | } 79 | 80 | func renderLogin(w http.ResponseWriter, id string, err error) { 81 | var errMsg string 82 | if err != nil { 83 | errMsg = err.Error() 84 | } 85 | data := &struct { 86 | ID string 87 | Error string 88 | }{ 89 | ID: id, 90 | Error: errMsg, 91 | } 92 | err = loginTmpl.Execute(w, data) 93 | if err != nil { 94 | http.Error(w, err.Error(), http.StatusInternalServerError) 95 | } 96 | } 97 | 98 | func (l *login) checkLoginHandler(w http.ResponseWriter, r *http.Request) { 99 | err := r.ParseForm() 100 | if err != nil { 101 | http.Error(w, fmt.Sprintf("cannot parse form:%s", err), http.StatusInternalServerError) 102 | return 103 | } 104 | username := r.FormValue("username") 105 | password := r.FormValue("password") 106 | id := r.FormValue("id") 107 | _, err = l.authenticate.CheckUsernamePassword(username, password, id) 108 | if err != nil { 109 | renderLogin(w, id, err) 110 | return 111 | } 112 | http.Redirect(w, r, l.callback(id), http.StatusFound) 113 | } 114 | -------------------------------------------------------------------------------- /example/jwtchat/jwtchat-idp/exampleop/op.go: -------------------------------------------------------------------------------- 1 | package exampleop 2 | 3 | import ( 4 | "context" 5 | "crypto/sha256" 6 | "log" 7 | "net/http" 8 | "os" 9 | 10 | "github.com/gorilla/mux" 11 | "golang.org/x/text/language" 12 | 13 | "github.com/zitadel/oidc/example/server/storage" 14 | "github.com/zitadel/oidc/pkg/op" 15 | ) 16 | 17 | const ( 18 | pathLoggedOut = "/logged-out" 19 | ) 20 | 21 | func init() { 22 | storage.RegisterClients( 23 | storage.NativeClient("native"), 24 | storage.WebClient("web", "secret"), 25 | storage.WebClient("api", "secret"), 26 | ) 27 | } 28 | 29 | type Storage interface { 30 | op.Storage 31 | CheckUsernamePassword(username, password, id string) (*interface{}, error) 32 | } 33 | 34 | // SetupServer creates an OIDC server with Issuer=http://localhost: 35 | // 36 | // Use one of the pre-made clients in storage/clients.go or register a new one. 37 | func SetupServer(ctx context.Context, issuer string, storage Storage) *mux.Router { 38 | // this will allow us to use an issuer with http:// instead of https:// 39 | os.Setenv(op.OidcDevMode, "true") 40 | 41 | // the OpenID Provider requires a 32-byte key for (token) encryption 42 | // be sure to create a proper crypto random key and manage it securely! 43 | key := sha256.Sum256([]byte("test")) 44 | 45 | router := mux.NewRouter() 46 | 47 | // for simplicity, we provide a very small default page for users who have signed out 48 | router.HandleFunc(pathLoggedOut, func(w http.ResponseWriter, req *http.Request) { 49 | _, err := w.Write([]byte("signed out successfully")) 50 | if err != nil { 51 | log.Printf("error serving logged out page: %v", err) 52 | } 53 | }) 54 | 55 | // creation of the OpenIDProvider with the just created in-memory Storage 56 | provider, err := newOP(ctx, storage, issuer, key) 57 | if err != nil { 58 | log.Fatal(err) 59 | } 60 | 61 | // the provider will only take care of the OpenID Protocol, so there must be some sort of UI for the login process 62 | // for the simplicity of the example this means a simple page with username and password field 63 | l := NewLogin(storage, op.AuthCallbackURL(provider)) 64 | 65 | // regardless of how many pages / steps there are in the process, the UI must be registered in the router, 66 | // so we will direct all calls to /login to the login UI 67 | router.PathPrefix("/login/").Handler(http.StripPrefix("/login", l.router)) 68 | 69 | // we register the http handler of the OP on the root, so that the discovery endpoint (/.well-known/openid-configuration) 70 | // is served on the correct path 71 | // 72 | // if your issuer ends with a path (e.g. http://localhost:9998/custom/path/), 73 | // then you would have to set the path prefix (/custom/path/) 74 | router.PathPrefix("/").Handler(provider.HttpHandler()) 75 | 76 | return router 77 | } 78 | 79 | // newOP will create an OpenID Provider for localhost on a specified port with a given encryption key 80 | // and a predefined default logout uri 81 | // it will enable all options (see descriptions) 82 | func newOP(ctx context.Context, storage op.Storage, issuer string, key [32]byte) (op.OpenIDProvider, error) { 83 | config := &op.Config{ 84 | Issuer: issuer, 85 | CryptoKey: key, 86 | 87 | // will be used if the end_session endpoint is called without a post_logout_redirect_uri 88 | DefaultLogoutRedirectURI: pathLoggedOut, 89 | 90 | // enables code_challenge_method S256 for PKCE (and therefore PKCE in general) 91 | CodeMethodS256: true, 92 | 93 | // enables additional client_id/client_secret authentication by form post (not only HTTP Basic Auth) 94 | AuthMethodPost: true, 95 | 96 | // enables additional authentication by using private_key_jwt 97 | AuthMethodPrivateKeyJWT: true, 98 | 99 | // enables refresh_token grant use 100 | GrantTypeRefreshToken: true, 101 | 102 | // enables use of the `request` Object parameter 103 | RequestObjectSupported: true, 104 | 105 | // this example has only static texts (in English), so we'll set the here accordingly 106 | SupportedUILocales: []language.Tag{language.English}, 107 | } 108 | handler, err := op.NewOpenIDProvider(ctx, config, storage, 109 | // as an example on how to customize an endpoint this will change the authorization_endpoint from /authorize to /auth 110 | op.WithCustomAuthEndpoint(op.NewEndpoint("auth")), 111 | ) 112 | if err != nil { 113 | return nil, err 114 | } 115 | return handler, nil 116 | } 117 | -------------------------------------------------------------------------------- /example/jwtchat/jwtchat-idp/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "github.com/openziti/sdk-golang/example/jwtchat/jwtchat-idp/exampleop" 6 | "github.com/openziti/sdk-golang/example/jwtchat/jwtchat-idp/storage" 7 | "log" 8 | "net/http" 9 | ) 10 | 11 | func main() { 12 | ctx := context.Background() 13 | 14 | storage := storage.NewStorage(storage.NewUserStore()) 15 | 16 | port := "9998" 17 | router := exampleop.SetupServer(ctx, "http://localhost:"+port, storage) 18 | 19 | server := &http.Server{ 20 | Addr: ":" + port, 21 | Handler: router, 22 | } 23 | log.Printf("server listening on http://localhost:%s/", port) 24 | log.Println("press ctrl+c to stop") 25 | err := server.ListenAndServe() 26 | if err != nil { 27 | log.Fatal(err) 28 | } 29 | <-ctx.Done() 30 | } 31 | -------------------------------------------------------------------------------- /example/jwtchat/jwtchat-idp/storage/token.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import "time" 4 | 5 | type Token struct { 6 | ID string 7 | ApplicationID string 8 | Subject string 9 | RefreshTokenID string 10 | Audience []string 11 | Expiration time.Time 12 | Scopes []string 13 | } 14 | 15 | type RefreshToken struct { 16 | ID string 17 | Token string 18 | AuthTime time.Time 19 | AMR []string 20 | Audience []string 21 | UserID string 22 | ApplicationID string 23 | Expiration time.Time 24 | Scopes []string 25 | } 26 | -------------------------------------------------------------------------------- /example/jwtchat/jwtchat-idp/storage/user.go: -------------------------------------------------------------------------------- 1 | package storage 2 | 3 | import ( 4 | "crypto/rsa" 5 | ) 6 | 7 | type User struct { 8 | ID string 9 | Username string 10 | Password string 11 | FirstName string 12 | LastName string 13 | Email string 14 | } 15 | 16 | type Service struct { 17 | keys map[string]*rsa.PublicKey 18 | } 19 | 20 | type UserStore interface { 21 | GetUserByID(string) *User 22 | GetUserByUsername(string) *User 23 | ExampleClientID() string 24 | } 25 | 26 | type userStore struct { 27 | users map[string]*User 28 | } 29 | 30 | func NewUserStore() UserStore { 31 | return userStore{ 32 | users: map[string]*User{ 33 | "id1": { 34 | ID: "id1", 35 | Username: "test1", 36 | Password: "test1", 37 | FirstName: "Test", 38 | LastName: "User", 39 | Email: "test1@example.com", 40 | }, 41 | }, 42 | } 43 | } 44 | 45 | // ExampleClientID is only used in the example server 46 | func (u userStore) ExampleClientID() string { 47 | return "service" 48 | } 49 | 50 | func (u userStore) GetUserByID(id string) *User { 51 | return u.users[id] 52 | } 53 | 54 | func (u userStore) GetUserByUsername(username string) *User { 55 | for _, user := range u.users { 56 | if user.Username == username { 57 | return user 58 | } 59 | } 60 | return nil 61 | } 62 | -------------------------------------------------------------------------------- /example/jwtchat/jwtchat-server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "flag" 6 | "fmt" 7 | "github.com/Jeffail/gabs" 8 | edge_apis "github.com/openziti/sdk-golang/edge-apis" 9 | "github.com/openziti/sdk-golang/ziti" 10 | "github.com/pkg/errors" 11 | log "github.com/sirupsen/logrus" 12 | "gopkg.in/resty.v1" 13 | "os" 14 | "os/signal" 15 | ) 16 | 17 | func main() { 18 | openzitiURL := flag.String("openziti-url", "https://localhost:1280", "URL of the OpenZiti service") 19 | idpTokenUrl := flag.String("idp-token-url", "http://localhost:9998/oauth/token", "URL of the Identity Provider") 20 | clientID := flag.String("client-id", "cid2", "Client ID for authentication") 21 | clientSecret := flag.String("client-secret", "cid2secret", "Client Secret for authentication") 22 | grantType := flag.String("grant-type", "client_credentials", "The grant type to use") 23 | scope := flag.String("scope", "openid", "The scope to use") 24 | 25 | // Parse flags 26 | flag.Parse() 27 | 28 | // Print values 29 | fmt.Println("OpenZiti URL\t:", *openzitiURL) 30 | fmt.Println("IDP URL\t\t:", *idpTokenUrl) 31 | fmt.Println("Client ID\t:", *clientID) 32 | fmt.Println("Client Secret\t:", *clientSecret) 33 | fmt.Println("Grant Type\t:", *grantType) 34 | fmt.Println("Scope\t\t:", *scope) 35 | 36 | c := make(chan os.Signal, 1) 37 | signal.Notify(c, os.Interrupt) 38 | 39 | jwtToken, err := getExternalJWT(*clientID, *clientSecret, *grantType, *scope, *idpTokenUrl) 40 | 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | caPool, err := ziti.GetControllerWellKnownCaPool(*openzitiURL) 46 | 47 | if err != nil { 48 | panic(err) 49 | } 50 | 51 | authenticator := edge_apis.NewJwtCredentials(jwtToken) 52 | authenticator.CaPool = caPool 53 | 54 | cfg := &ziti.Config{ 55 | ZtAPI: *openzitiURL + "/edge/client/v1", 56 | Credentials: authenticator, 57 | } 58 | ctx, err := ziti.NewContext(cfg) 59 | 60 | if err != nil { 61 | panic(err) 62 | } 63 | 64 | err = ctx.Authenticate() 65 | 66 | if err != nil { 67 | panic(err) 68 | } 69 | 70 | svcs, err := ctx.GetServices() 71 | 72 | if err != nil { 73 | panic(err) 74 | } 75 | 76 | found := false 77 | for _, svc := range svcs { 78 | if *svc.Name == "jwtchat" { 79 | found = true 80 | break 81 | } 82 | } 83 | if !found { 84 | panic("jwtchat service not found") 85 | } 86 | 87 | listener, err := ctx.Listen("jwtchat") 88 | 89 | if err != nil { 90 | panic(err) 91 | } 92 | 93 | log.Println("listening for service: jwtchat") 94 | 95 | defer func() { 96 | _ = listener.Close() 97 | }() 98 | 99 | go func() { 100 | for { 101 | conn, err := listener.Accept() 102 | 103 | if err != nil { 104 | log.Errorf("error accepting connection: %s", err) 105 | } 106 | 107 | if listener.IsClosed() { 108 | return 109 | } 110 | 111 | go func() { 112 | byteBuffer := make([]byte, 128) 113 | 114 | for { 115 | n, err := conn.Read(byteBuffer) 116 | 117 | if err != nil { 118 | log.Errorf("error reading, exiting: %s", err) 119 | return 120 | } 121 | 122 | if n != 0 { 123 | fmt.Printf("client: %s", string(byteBuffer[0:n])) 124 | } 125 | } 126 | }() 127 | 128 | go func() { 129 | reader := bufio.NewReader(os.Stdin) 130 | for { 131 | fmt.Print("-> ") 132 | text, _ := reader.ReadString('\n') 133 | 134 | _, err := conn.Write([]byte(text)) 135 | 136 | if err != nil { 137 | log.Errorf("error writing, exiting: %s", err) 138 | return 139 | } 140 | } 141 | }() 142 | 143 | } 144 | }() 145 | 146 | <-c 147 | 148 | return 149 | } 150 | 151 | // getExternalJWT will use Open ID Connect's client credentials flow to obtain a JWT from the jwtchat-idp executable. 152 | func getExternalJWT(clientId string, clientSecret string, grantType string, scope string, idpTokenUrl string) (string, error) { 153 | resp, err := resty.R().SetFormData(map[string]string{ 154 | "client_secret": clientSecret, 155 | "client_id": clientId, 156 | "grant_type": grantType, 157 | "scope": scope, 158 | }).Post(idpTokenUrl) 159 | 160 | if err != nil { 161 | return "", err 162 | } 163 | json := resp.Body() 164 | jsonContainer, err := gabs.ParseJSON(json) 165 | 166 | if err != nil { 167 | return "", err 168 | } 169 | 170 | tokenName := "access_token" 171 | if !jsonContainer.ExistsP(tokenName) { 172 | return "", errors.New("no " + tokenName + " property found") 173 | } 174 | 175 | token, ok := jsonContainer.Path(tokenName).Data().(string) 176 | if !ok { 177 | return "", errors.New(tokenName + " was not a valid JSON string") 178 | } 179 | 180 | return token, nil 181 | } 182 | -------------------------------------------------------------------------------- /example/reflect/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang 2 | RUN go get -u github.com/openziti/sdk-golang/example/reflect 3 | ENV GO111MODULE=on 4 | ENV GOFLAGS=-mod=vendor 5 | ENV APP_USER=appuser 6 | ENV APP_GROUP=appgroup 7 | ENV APP_HOME=/app 8 | ARG GROUP_ID=1000 9 | ARG USER_ID=1000 10 | RUN groupadd --gid $GROUP_ID $APP_GROUP && useradd -m -l --uid $USER_ID --gid $GROUP_ID $APP_USER 11 | RUN mkdir -p $APP_HOME 12 | RUN chown -R $APP_USER:$APP_GROUP $APP_HOME 13 | RUN chmod -R 0777 $APP_HOME 14 | USER $APP_USER 15 | WORKDIR $APP_HOME 16 | VOLUME /identity 17 | EXPOSE 8010 18 | CMD reflect server --verbose --identity=/identity/${IDENTITY_FILE} --serviceName="${SERVICE_NAME}" 19 | -------------------------------------------------------------------------------- /example/reflect/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This example is a simple reflect server. The client sends some bytes to the server, and the server responds with those bytes. 3 | 4 | This example demonstrates: 5 | * Binding a service and listening for connections 6 | * Dialing a service 7 | * Bidirectional communication over the Open Ziti network overlay 8 | 9 | ## Requirements 10 | * an OpenZiti network. If you do not have one, the [quickstart](https://openziti.github.io/ziti/quickstarts/quickstart-overview.html) works well 11 | * OpenZiti cli or Zac to create services and identities on the OpenZiti network 12 | 13 | ## Build the examples 14 | Refer to the [example README](../README.md) to build the SDK examples 15 | 16 | ## Setup using the OpenZiti CLI 17 | These steps will configure the reflect service using the OpenZiti CLI. At the end of these steps you will have created: 18 | * a service called `reflectService` 19 | * an identity to host (bind) the service 20 | * an identity to connect to (dial) the service 21 | * the service policies required to run the application 22 | 23 | Steps: 24 | 1. Log into OpenZiti. The host:port and username/password will vary depending on your network. 25 | 26 | ziti edge login localhost:1280 -u admin -p admin 27 | 2. Run this script to create everything you need. 28 | 29 | echo Changing to build directory 30 | cd $ZITI_SDK_BUILD_DIR 31 | 32 | echo Create the service 33 | ziti edge create service reflectService --role-attributes reflect-service 34 | 35 | echo Create and enroll two identities 36 | ziti edge create identity device reflect-client -a reflect.clients -o reflect-client.jwt 37 | ziti edge create identity device reflect-server -a reflect.servers -o reflect-server.jwt 38 | ziti edge enroll --jwt reflect-client.jwt 39 | ziti edge enroll --jwt reflect-server.jwt 40 | 41 | echo Create service policies 42 | ziti edge create service-policy reflect-client-dial Dial --identity-roles '#reflect.clients' --service-roles '#reflect-service' 43 | ziti edge create service-policy reflect-client-bind Bind --identity-roles '#reflect.servers' --service-roles '#reflect-service' 44 | 45 | echo Run policy advisor to check 46 | ziti edge policy-advisor services 47 | 3. Run the server. 48 | 49 | ./reflect server -i reflect-server.json -s reflectService 50 | 4. Run the client. 51 | 52 | ./reflect client -i reflect-client.json -s reflectService 53 | ### Example output 54 | **Server** 55 | ```shell 56 | $ ./reflect server -i server.json -s reflect_svc 57 | INFO ready to accept connections 58 | INFO connection to edge router using api session token ae0a33d9-e745-4b8e-b7df-9a5c850e2222 59 | INFO new connection accepted 60 | INFO about to read a string : 61 | INFO read : Hello Ziti 62 | INFO responding with : you sent me: Hello Ziti 63 | ``` 64 | **Client** 65 | ```shell 66 | $ ./reflect client -i client.json -s reflect_svc 67 | INFO found service named: reflect_svc 68 | WARNING no config of type ziti-tunneler-client.v1 was found 69 | INFO connection to edge router using api session token b97826dc-5314-44fb-9407-b6177f409b68 70 | INFO Connected to reflect_svc successfully. 71 | INFO You may now type a line to be sent to the server (press enter to send) 72 | INFO The line will be sent to the reflect server and returned 73 | Hello Ziti 74 | wrote 11 bytes 75 | Sent :Hello Ziti 76 | Received: you sent me: Hello Ziti 77 | ``` 78 | 79 | ## Teardown 80 | Done with the example? This script will remove everything created during setup. 81 | ```shell 82 | echo Removing service policies 83 | ziti edge delete service-policy reflect-client-dial 84 | ziti edge delete service-policy reflect-client-bind 85 | 86 | echo Removing identities 87 | ziti edge delete identity reflect-client 88 | ziti edge delete identity reflect-server 89 | 90 | echo Removing service 91 | ziti edge delete service reflectService 92 | ``` 93 | -------------------------------------------------------------------------------- /example/reflect/cmd/client.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/openziti/sdk-golang/ziti" 7 | "os" 8 | ) 9 | 10 | func Client(zitiCfg *ziti.Config, serviceName string) { 11 | ctx, err := ziti.NewContext(zitiCfg) //get a ziti context using a file 12 | 13 | if err != nil { 14 | panic(err) 15 | } 16 | 17 | foundSvc, ok := ctx.GetService(serviceName) 18 | if !ok { 19 | panic("error when retrieving all the services for the provided config") 20 | } 21 | log.Infof("found service named: %s", *foundSvc.Name) 22 | 23 | svc, err := ctx.Dial(serviceName) //dial the service using the given name 24 | if err != nil { 25 | panic(fmt.Sprintf("error when dialing service name %s. %v", serviceName, err)) 26 | } 27 | log.Infof("Connected to %s successfully.", serviceName) 28 | log.Info("You may now type a line to be sent to the server (press enter to send)") 29 | log.Info("The line will be sent to the reflect server and returned") 30 | 31 | reader := bufio.NewReader(os.Stdin) //setup a reader for reading input from the commandline 32 | 33 | conRead := bufio.NewReader(svc) 34 | conWrite := bufio.NewWriter(svc) 35 | 36 | for { 37 | text, err := reader.ReadString('\n') //read a line from input 38 | if err != nil { 39 | fmt.Println(err) 40 | } 41 | bytesRead, err := conWrite.WriteString(text) 42 | _ = conWrite.Flush() 43 | if err != nil { 44 | fmt.Println(err) 45 | } else { 46 | fmt.Println("wrote", bytesRead, "bytes") 47 | } 48 | fmt.Print("Sent :", text) 49 | read, err := conRead.ReadString('\n') 50 | if err != nil { 51 | fmt.Println(err) 52 | } else { 53 | fmt.Println("Received:", read) 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /example/reflect/cmd/pkg-vars.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "github.com/michaelquigley/pfxlog" 4 | 5 | var log = pfxlog.Logger() 6 | -------------------------------------------------------------------------------- /example/reflect/cmd/server.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/openziti/sdk-golang/ziti" 7 | "net" 8 | "os" 9 | "strings" 10 | ) 11 | 12 | func Server(zitiCfg *ziti.Config, serviceName string) { 13 | ctx, err := ziti.NewContext(zitiCfg) 14 | 15 | if err != nil { 16 | panic(err) 17 | } 18 | 19 | listener, err := ctx.Listen(serviceName) 20 | if err != nil { 21 | log.Panic(err) 22 | } 23 | serve(listener) 24 | 25 | sig := make(chan os.Signal) 26 | s := <-sig 27 | log.Infof("received %s: shutting down...", s) 28 | } 29 | 30 | func serve(listener net.Listener) { 31 | log.Infof("ready to accept connections") 32 | for { 33 | conn, _ := listener.Accept() 34 | log.Infof("new connection accepted") 35 | go accept(conn) 36 | } 37 | } 38 | 39 | func accept(conn net.Conn) { 40 | if conn == nil { 41 | panic("connection is nil!") 42 | } 43 | writer := bufio.NewWriter(conn) 44 | reader := bufio.NewReader(conn) 45 | rw := bufio.NewReadWriter(reader, writer) 46 | //line delimited 47 | for { 48 | line, err := rw.ReadString('\n') 49 | if err != nil { 50 | log.Error(err) 51 | break 52 | } 53 | log.Info("about to read a string :") 54 | log.Infof(" read : %s", strings.TrimSpace(line)) 55 | resp := fmt.Sprintf("you sent me: %s", line) 56 | _, _ = rw.WriteString(resp) 57 | _ = rw.Flush() 58 | log.Infof(" responding with : %s", strings.TrimSpace(resp)) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/reflect/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # docker-compose build --build-arg USER_ID=$(id -u) --build-arg GROUP_ID=$(id -g) reflect-server 2 | # IDENTITY_FILE=MyReflectSrv1.json SERVICE_NAME="My Reflect Server" docker-compose up reflect-server 3 | version: "3.3" 4 | services: 5 | reflect-server: 6 | # image: netfoundry/reflect-server 7 | build: 8 | context: . 9 | restart: unless-stopped 10 | volumes: 11 | - .:/identity 12 | environment: 13 | - IDENTITY_FILE # JSON file in same dir as this Compose file 14 | - SERVICE_NAME # Ziti service name to bind e.g. "ACME Reflect Server" 15 | -------------------------------------------------------------------------------- /example/reflect/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/michaelquigley/pfxlog" 5 | "github.com/openziti/sdk-golang/example/reflect/cmd" 6 | "github.com/openziti/sdk-golang/ziti" 7 | "github.com/sirupsen/logrus" 8 | "github.com/spf13/cobra" 9 | ) 10 | 11 | var log = pfxlog.Logger() 12 | var verbose bool 13 | 14 | var rootCmd = &cobra.Command{Use: "app"} 15 | 16 | func main() { 17 | logrus.SetFormatter(&logrus.TextFormatter{ 18 | ForceColors: true, 19 | DisableTimestamp: true, 20 | TimestampFormat: "", 21 | PadLevelText: true, 22 | }) 23 | logrus.SetReportCaller(false) 24 | 25 | rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) { 26 | if verbose { 27 | logrus.SetLevel(logrus.DebugLevel) 28 | } 29 | } 30 | rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging") 31 | rootCmd.PersistentFlags().StringP("identity", "i", "", "REQUIRED: Path to JSON file that contains an enrolled identity") 32 | rootCmd.PersistentFlags().StringP("serviceName", "s", "", "REQUIRED: The service to host") 33 | 34 | _ = cobra.MarkFlagRequired(rootCmd.PersistentFlags(), "identity") 35 | _ = cobra.MarkFlagRequired(rootCmd.PersistentFlags(), "serviceName") 36 | 37 | var serverCmd = &cobra.Command{ 38 | Use: "server", 39 | Short: "run the process as a server", 40 | Run: func(subcmd *cobra.Command, args []string) { 41 | cmd.Server(getConfig(), rootCmd.Flag("serviceName").Value.String()) 42 | }, 43 | } 44 | 45 | var clientCmd = &cobra.Command{ 46 | Use: "client", 47 | Short: "run the process as a client", 48 | Run: func(subcmd *cobra.Command, args []string) { 49 | cmd.Client(getConfig(), rootCmd.Flag("serviceName").Value.String()) 50 | }, 51 | } 52 | 53 | rootCmd.AddCommand(clientCmd, serverCmd) 54 | _ = rootCmd.Execute() 55 | } 56 | 57 | func getConfig() (zitiCfg *ziti.Config) { 58 | identityJson := rootCmd.Flag("identity").Value.String() 59 | zitiCfg, err := ziti.NewConfigFromFile(identityJson) 60 | if err != nil { 61 | log.Fatalf("failed to load ziti configuration file: %v", err) 62 | } 63 | zitiCfg.ConfigTypes = []string{ 64 | "ziti-tunneler-client.v1", 65 | } 66 | return zitiCfg 67 | } 68 | -------------------------------------------------------------------------------- /example/simple-server/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This example illustrates how to embed zero trust connectivity into your server-side code. The server now listens on the 3 | OpenZiti overlay network and not on the layer 3, IP-based network. 4 | 5 | This example demonstrates: 6 | * Binding a service and listening for HTTP connections 7 | * Accessing the service via a tunneler 8 | 9 | ## Requirements 10 | * an OpenZiti network. If you do not have one, you can use one of the [quickstarts](https://openziti.github.io/ziti/quickstarts/quickstart-overview.html) to set one up. 11 | * OpenZiti CLI to create services and identities on the OpenZiti Network 12 | * Have the appropriate [Ziti Desktop Edge](https://openziti.github.io/ziti/clients/which-client.html) for your operating system 13 | 14 | ## Build the examples 15 | Refer to the [example README](../README.md) to build the SDK examples 16 | 17 | ## Setup using the OpenZiti CLI 18 | These steps will configure the service using the OpenZiti CLI. At the end of these steps you will have created: 19 | * a service called `simpleService` 20 | * an identity to host (bind) the service 21 | * an identity to connect to (dial) the service 22 | * the service configs to connect the service to the overlay 23 | * the service policies required to authorize the identities for bind and dial 24 | 25 | Steps: 26 | 1. Log into OpenZiti. The host:port and username/password will vary depending on your network. 27 | 28 | ziti edge login localhost:1280 -u admin -p admin 29 | 1. Run this script to create everything you need. 30 | 31 | cd /example/build 32 | 33 | echo Create the service configs 34 | ziti edge create config simple.hostv1 host.v1 '{"protocol":"tcp", "address":"localhost","port":'8080'}' 35 | ziti edge create config simple.interceptv1 intercept.v1 '{"protocols":["tcp"],"addresses":["simpleService.ziti"], "portRanges":[{"low":'8080', "high":'8080'}]}' 36 | 37 | echo Create the service 38 | ziti edge create service simpleService --configs "simple.hostv1,simple.interceptv1" --role-attributes simple-service 39 | 40 | echo Create two identities and enroll the server 41 | ziti edge create identity user simple-client -a simpleserver.clients -o simple-client.jwt 42 | ziti edge create identity device simple-server -a simpleserver.servers -o simple-server.jwt 43 | ziti edge enroll --jwt simple-server.jwt 44 | 45 | echo Create service policies 46 | ziti edge create service-policy simple-client-dial Dial --identity-roles '#simpleserver.clients' --service-roles '#simple-service' 47 | ziti edge create service-policy simple-client-bind Bind --identity-roles '#simpleserver.servers' --service-roles '#simple-service' 48 | 49 | echo Run policy advisor to check 50 | ziti edge policy-advisor services 51 | 1. Run the server. 52 | 53 | ./simple-server simple-server.json simpleService 54 | 55 | 1. Enroll the `simple-client` client identity 56 | 1. Refer to [enrolling documentation](https://openziti.github.io/ziti/identities/enrolling.html) for details 57 | 58 | 1. Issue cURL commands to see the server side responses in action. There are two servers spun up by the `simple-server` 59 | binary. One server is a simple HTTP server which is running on the local machine. The second server is a zitified 60 | HTTP server, this server should be accessible from the device running ZDE where you enrolled the `simple-client` 61 | identity. 62 | 63 | # curl to the server listening on the underlay: 64 | curl http://localhost:8080?name=client 65 | 66 | # curl to the server listening on the overlay: 67 | curl http://simpleService.ziti:8080?name=client 68 | 69 | ### Example output 70 | The following is the output you'll see from the server and client side after running the previous commands. 71 | **Server** 72 | ``` 73 | $ ./simple-server simple-server.json simpleService 74 | listening for non-ziti requests on localhost:8080 75 | listening for requests for Ziti service simpleService 76 | Saying hello to client, coming in from plain-internet 77 | Saying hello to client, coming in from ziti 78 | ``` 79 | **Client** 80 | ``` 81 | $ curl http://localhost:8080?name=client 82 | Hello, client, from plain-internet 83 | 84 | $ curl http://simpleService.ziti:8080?name=client 85 | Hello, client, from ziti 86 | ``` 87 | 88 | ## Teardown 89 | Done with the example? This script will remove everything created during setup. 90 | You will have to manually remove the identity from your Ziti Desktop Edge application. 91 | ``` 92 | ziti edge login localhost:1280 -u admin -p admin 93 | 94 | echo Removing service policies 95 | ziti edge delete service-policy simple-client-dial 96 | ziti edge delete service-policy simple-client-bind 97 | 98 | echo Removing service configs 99 | ziti edge delete config simple.hostv1 100 | ziti edge delete config simple.interceptv1 101 | 102 | echo Removing identities 103 | ziti edge delete identity simple-client 104 | ziti edge delete identity simple-server 105 | 106 | echo Removing service 107 | ziti edge delete service simpleService 108 | ``` 109 | -------------------------------------------------------------------------------- /example/simple-server/simple-server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/michaelquigley/pfxlog" 22 | "github.com/openziti/sdk-golang/ziti" 23 | "github.com/sirupsen/logrus" 24 | "net" 25 | "net/http" 26 | "os" 27 | "time" 28 | ) 29 | 30 | type Greeter string 31 | 32 | func (g Greeter) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 33 | var result string 34 | if name := req.URL.Query().Get("name"); name != "" { 35 | result = fmt.Sprintf("Hello, %v, from %v\n", name, g) 36 | fmt.Printf("Saying hello to %v, coming in from %v\n", name, g) 37 | } else { 38 | result = "Who are you?\n" 39 | fmt.Println("Asking for introduction") 40 | } 41 | if _, err := resp.Write([]byte(result)); err != nil { 42 | panic(err) 43 | } 44 | } 45 | 46 | func serve(listener net.Listener, serverType string) { 47 | if err := http.Serve(listener, Greeter(serverType)); err != nil { 48 | panic(err) 49 | } 50 | } 51 | 52 | func httpServer(listenAddr string) { 53 | listener, err := net.Listen("tcp", listenAddr) 54 | if err != nil { 55 | panic(err) 56 | } 57 | fmt.Printf("listening for non-ziti requests on %v\n", listenAddr) 58 | serve(listener, "plain-internet") 59 | } 60 | 61 | func zitifiedServer() { 62 | options := ziti.ListenOptions{ 63 | ConnectTimeout: 5 * time.Minute, 64 | MaxConnections: 3, 65 | } 66 | 67 | // Get identity config 68 | cfg, err := ziti.NewConfigFromFile(os.Args[1]) 69 | if err != nil { 70 | panic(err) 71 | } 72 | 73 | // Get service name (defaults to "simpleService") 74 | serviceName := "simpleService" 75 | if len(os.Args) > 2 { 76 | serviceName = os.Args[2] 77 | fmt.Printf("Using the provided service name [%v]", serviceName) 78 | } else { 79 | fmt.Printf("Using the default service [%v]", serviceName) 80 | } 81 | 82 | ctx, err := ziti.NewContext(cfg) 83 | 84 | if err != nil { 85 | panic(err) 86 | } 87 | 88 | listener, err := ctx.ListenWithOptions(serviceName, &options) 89 | if err != nil { 90 | fmt.Printf("Error binding service %+v\n", err) 91 | panic(err) 92 | } 93 | 94 | fmt.Printf("listening for requests for Ziti service %v\n", serviceName) 95 | serve(listener, "ziti") 96 | } 97 | 98 | func main() { 99 | if os.Getenv("DEBUG") == "true" { 100 | pfxlog.GlobalInit(logrus.DebugLevel, pfxlog.DefaultOptions()) 101 | pfxlog.Logger().Debugf("debug enabled") 102 | } 103 | 104 | // Startup zitified server and plain http server 105 | go zitifiedServer() 106 | httpServer("localhost:8080") 107 | } 108 | -------------------------------------------------------------------------------- /example/udp-offload/README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | This example illustrates how to send data to, and receive data from a UDP server via OpenZiti. 3 | You will configure an OpenZiti overlay network and run a UDP server that will respond with whatever text it was sent. 4 | The response from the server will be read written to the console. 5 | 6 | This example demonstrates a hybrid approach ZTAA --> ZTHA: 7 | * Dialing a service 8 | * Binding a service using a tunneler 9 | 10 | ## Requirements 11 | * OpenZiti CLI to create services and identities on the OpenZiti Network 12 | * an OpenZiti network. If you have one you'd like to use, great. The guide is written using the 13 | `ziti edge quickstart` command. 14 | * All commands are executed relative to the `example` folder 15 | 16 | ## Build the examples 17 | Refer to the [example README](../README.md) to build the SDK examples 18 | 19 | ## Run and Configure OpenZiti 20 | The README assumes the `ziti` CLI on your path. If not, supply the full path to the `ziti` executable. This command 21 | will start a ziti overlay network on ports 1280/3022 for use with the rest of the README. The default values will 22 | also be used for username and password. The router from the quickstart is the identity which will offload the OpenZiti 23 | traffic toward the UDP server 24 | 25 | In a new terminal run the following command: 26 | ``` 27 | ziti edge quickstart 28 | ``` 29 | 30 | To configure the overlay, you will need another terminal with `ziti` on the path. Now, add a service for the UDP 31 | server to be offloaded from the OpenZiti overlay as well as create the identity this example will use: 32 | ``` 33 | svc_name="udp.relay.example" 34 | edge_router_name="quickstart-router" 35 | ziti edge login localhost:1280 -u admin -p admin -y 36 | ziti edge create config ${svc_name}.hostv1 host.v1 '{"protocol":"udp", "address":"127.0.0.1","port":10001}' 37 | ziti edge create service ${svc_name} --configs "${svc_name}.hostv1" 38 | ziti edge create service-policy ${svc_name}.dial Dial --identity-roles "#${svc_name}.dialers" --service-roles "@${svc_name}" 39 | ziti edge create service-policy ${svc_name}.bind Bind --identity-roles "#${svc_name}.binders" --service-roles "@${svc_name}" 40 | 41 | ziti edge create identity ${svc_name}.client -a ${svc_name}.dialers -o ${svc_name}.client.jwt 42 | ziti edge enroll --jwt ${svc_name}.client.jwt 43 | 44 | ziti edge update identity ${edge_router_name} -a "${svc_name}.binders" 45 | ziti edge policy-advisor services -q 46 | ``` 47 | 48 | ## Run the UDP Server 49 | In the terminal from where you configured the OpenZiti overlay start the UDP server. Make sure you're in the 50 | `example` folder and run: 51 | ``` 52 | ./build/udp-server 53 | ``` 54 | 55 | You should now have a UDP server that is listening on port 10001 and will respond to UDP messages sent to it. 56 | ``` 57 | $ ./build/udp-server 58 | Listening on :10001 59 | ``` 60 | 61 | ## Run the Example 62 | Make sure the router (or identity) hosting the service establishes a terminator. Issue the following command and verify 63 | a terminator is listed as shown: 64 | ``` 65 | ziti edge list terminators 'service.name="udp.relay.example"' 66 | ``` 67 | 68 | example output: 69 | ``` 70 | $ ziti edge list terminators 'service.name="udp.relay.example"' 71 | ╭───────────────────────┬───────────────────┬───────────────────┬─────────┬───────────────────────┬──────────┬──────┬────────────┬──────────────╮ 72 | │ ID │ SERVICE │ ROUTER │ BINDING │ ADDRESS │ IDENTITY │ COST │ PRECEDENCE │ DYNAMIC COST │ 73 | ├───────────────────────┼───────────────────┼───────────────────┼─────────┼───────────────────────┼──────────┼──────┼────────────┼──────────────┤ 74 | │ sNVBPDKuI6q5I0f2PrEc6 │ udp.relay.example │ quickstart-router │ tunnel │ sNVBPDKuI6q5I0f2PrEc6 │ │ 0 │ default │ 0 │ 75 | ╰───────────────────────┴───────────────────┴───────────────────┴─────────┴───────────────────────┴──────────┴──────┴────────────┴──────────────╯ 76 | results: 1-1 of 1 77 | ``` 78 | 79 | With the terminator in place, run the sample 80 | ``` 81 | ./build/udp-offload-client ./udp.relay.example.client.json 82 | ``` 83 | 84 | example output: 85 | ``` 86 | $ ./build/udp-offload-client ./udp.relay.example.client.json 87 | INFO[0000] found service named: udp.relay.example 88 | INFO[0000] Connected to udp.relay.example successfully. 89 | INFO[0000] You may now type a line to be sent to the server (press enter to send) 90 | INFO[0000] The line will be sent to the reflect server and returned 91 | this is the udp example 92 | wrote 24 bytes 93 | Sent :this is the udp example 94 | Received: udp server echo: this is the udp example 95 | ``` -------------------------------------------------------------------------------- /example/udp-offload/udp-offload-client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "github.com/michaelquigley/pfxlog" 7 | "github.com/openziti/sdk-golang/ziti" 8 | "io" 9 | "os" 10 | ) 11 | 12 | var log = pfxlog.Logger() 13 | 14 | func main() { 15 | serviceName := "udp.relay.example" 16 | 17 | zitiCfg, err := ziti.NewConfigFromFile(os.Args[1]) 18 | if err != nil { 19 | log.Fatalf("failed to load ziti configuration file: %v", err) 20 | } 21 | zitiCfg.ConfigTypes = []string{ 22 | "ziti-tunneler-client.v1", 23 | } 24 | 25 | ctx, err := ziti.NewContext(zitiCfg) 26 | if err != nil { 27 | fmt.Println(err) 28 | os.Exit(1) 29 | } 30 | 31 | foundSvc, ok := ctx.GetService(serviceName) 32 | if !ok { 33 | fmt.Println("error when retrieving all the services for the provided config") 34 | os.Exit(1) 35 | } 36 | log.Infof("found service named: %s", *foundSvc.Name) 37 | 38 | svc, err := ctx.Dial(serviceName) //dial the service using the given name 39 | if err != nil { 40 | fmt.Println(fmt.Sprintf("error when dialing service name %s. %v", serviceName, err)) 41 | os.Exit(1) 42 | } 43 | 44 | go ReadFromZiti(svc) 45 | log.Infof("Connected to %s successfully.", serviceName) 46 | log.Info("You may now type a line to be sent to the server (press enter to send)") 47 | log.Info("The line will be sent to the reflect server and returned") 48 | ReadFromConsole(svc) 49 | } 50 | 51 | func ReadFromConsole(writer io.Writer) { 52 | conWrite := bufio.NewWriter(writer) 53 | reader := bufio.NewReader(os.Stdin) 54 | for { 55 | text, err := reader.ReadString('\n') //read a line from input 56 | if err != nil { 57 | fmt.Println(err) 58 | os.Exit(1) 59 | } 60 | bytesRead, err := conWrite.WriteString(text) 61 | _ = conWrite.Flush() 62 | if err != nil { 63 | fmt.Println(err) 64 | os.Exit(1) 65 | } else { 66 | fmt.Println("wrote", bytesRead, "bytes") 67 | } 68 | fmt.Print("Sent :", text) 69 | } 70 | } 71 | 72 | func ReadFromZiti(reader io.Reader) { 73 | conRead := bufio.NewReader(reader) 74 | for { 75 | read, err := conRead.ReadString('\n') 76 | if err != nil { 77 | fmt.Println(err) 78 | os.Exit(1) 79 | } else { 80 | fmt.Print("Received: ", read) 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example/udp-offload/udp-server/udp-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | addr := ":10001" 11 | conn, err := net.ListenPacket("udp", addr) 12 | if err != nil { 13 | fmt.Println(err) 14 | os.Exit(1) 15 | } 16 | defer conn.Close() 17 | fmt.Printf("Listening on %s\n", addr) 18 | 19 | buf := make([]byte, 1024) 20 | for { 21 | n, remoteAddr, err := conn.ReadFrom(buf) 22 | if err != nil { 23 | fmt.Println("Error reading:", err) 24 | continue 25 | } 26 | 27 | fmt.Printf("%s sent: %s\n", remoteAddr, string(buf[:n-1])) 28 | 29 | _, err = conn.WriteTo([]byte("udp server echo: "), remoteAddr) 30 | if err != nil { 31 | fmt.Println("Error writing:", err) 32 | } 33 | _, err = conn.WriteTo(buf[:n], remoteAddr) 34 | if err != nil { 35 | fmt.Println("Error writing:", err) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/zcat/zcat.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/michaelquigley/pfxlog" 22 | "github.com/openziti/foundation/v2/info" 23 | "github.com/openziti/sdk-golang/ziti" 24 | "github.com/openziti/transport/v2" 25 | "github.com/sirupsen/logrus" 26 | "github.com/spf13/cobra" 27 | "io" 28 | "net/http" 29 | "net/url" 30 | "os" 31 | "time" 32 | ) 33 | 34 | func init() { 35 | pfxlog.GlobalInit(logrus.InfoLevel, pfxlog.DefaultOptions().SetTrimPrefix("github.com/openziti/")) 36 | } 37 | 38 | var verbose bool 39 | var logFormatter string 40 | var retry bool 41 | var identityFile string 42 | var ctrlProxy string 43 | var routerProxy string 44 | 45 | func init() { 46 | root.PersistentFlags().StringVarP(&identityFile, "identity", "i", "", "Identity file path") 47 | root.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "Enable verbose logging") 48 | root.PersistentFlags().BoolVarP(&retry, "retry", "r", false, "Retry after i/o error") 49 | root.PersistentFlags().StringVar(&logFormatter, "log-formatter", "", "Specify log formatter [json|pfxlog|text]") 50 | root.PersistentFlags().StringVar(&ctrlProxy, "ctrl-proxy", "", "Specify a proxy to use for controller connections") 51 | root.PersistentFlags().StringVar(&routerProxy, "router-proxy", "", "Specify a proxy to use for router connections") 52 | } 53 | 54 | var root = &cobra.Command{ 55 | Use: "zcat ", 56 | Short: "Ziti Netcat", 57 | PersistentPreRun: func(cmd *cobra.Command, args []string) { 58 | if verbose { 59 | logrus.SetLevel(logrus.DebugLevel) 60 | } 61 | 62 | switch logFormatter { 63 | case "pfxlog": 64 | logrus.SetFormatter(pfxlog.NewFormatter(pfxlog.DefaultOptions().StartingToday())) 65 | case "json": 66 | logrus.SetFormatter(&logrus.JSONFormatter{}) 67 | case "text": 68 | logrus.SetFormatter(&logrus.TextFormatter{}) 69 | default: 70 | // let logrus do its own thing 71 | } 72 | }, 73 | Args: cobra.RangeArgs(1, 2), 74 | Run: runFunc, 75 | } 76 | 77 | func main() { 78 | if err := root.Execute(); err != nil { 79 | fmt.Printf("error: %s", err) 80 | } 81 | } 82 | 83 | func runFunc(_ *cobra.Command, args []string) { 84 | log := pfxlog.Logger() 85 | service := args[0] 86 | 87 | // Get identity config 88 | cfg, err := ziti.NewConfigFromFile(identityFile) 89 | if err != nil { 90 | panic(err) 91 | } 92 | 93 | if ctrlProxy != "" { 94 | fmt.Printf("using controller proxy: %s\n", ctrlProxy) 95 | cfg.CtrlProxy = func(request *http.Request) (*url.URL, error) { 96 | return url.Parse(ctrlProxy) 97 | } 98 | } 99 | 100 | if routerProxy != "" { 101 | fmt.Printf("using router proxy: %s\n", routerProxy) 102 | cfg.RouterProxy = func(addr string) *transport.ProxyConfiguration { 103 | return &transport.ProxyConfiguration{ 104 | Type: transport.ProxyTypeHttpConnect, 105 | Address: routerProxy, 106 | } 107 | } 108 | } 109 | 110 | context, err := ziti.NewContext(cfg) 111 | 112 | if err != nil { 113 | panic(err) 114 | } 115 | 116 | for { 117 | opts := &ziti.DialOptions{ 118 | ConnectTimeout: 5 * time.Second, 119 | } 120 | if len(args) >= 2 { 121 | opts.Identity = args[1] 122 | } 123 | conn, err := context.DialWithOptions(service, opts) 124 | if err != nil { 125 | if retry { 126 | log.WithError(err).Errorf("unable to dial service: '%v'", service) 127 | log.Info("retrying in 5 seconds") 128 | time.Sleep(5 * time.Second) 129 | } else { 130 | log.WithError(err).Fatalf("unable to dial service: '%v'", service) 131 | } 132 | } else { 133 | pfxlog.Logger().Info("connected") 134 | go Copy(conn, os.Stdin) 135 | Copy(os.Stdout, conn) 136 | _ = conn.Close() 137 | if !retry { 138 | return 139 | } 140 | } 141 | } 142 | } 143 | 144 | func Copy(writer io.Writer, reader io.Reader) { 145 | buf := make([]byte, info.MaxUdpPacketSize) 146 | bytesCopied, err := io.CopyBuffer(writer, reader, buf) 147 | pfxlog.Logger().Infof("Copied %v bytes", bytesCopied) 148 | if err != nil { 149 | pfxlog.Logger().Errorf("error while copying bytes (%v)", err) 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /example/zping/enroll.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | "github.com/michaelquigley/pfxlog" 23 | "github.com/openziti/sdk-golang/ziti" 24 | "github.com/openziti/sdk-golang/ziti/enroll" 25 | "github.com/pkg/errors" 26 | "github.com/sirupsen/logrus" 27 | "io/ioutil" 28 | "os" 29 | "strings" 30 | 31 | "github.com/spf13/cobra" 32 | ) 33 | 34 | func processEnrollment(jwtpath, outpath string) error { 35 | var keyAlg ziti.KeyAlgVar = "RSA" 36 | var keyPath, certPath, idname, caOverride string 37 | 38 | if strings.TrimSpace(outpath) == "" { 39 | out, outErr := outPathFromJwt(jwtpath) 40 | if outErr != nil { 41 | return fmt.Errorf("could not set the output path: %s", outErr) 42 | } 43 | outpath = out 44 | } 45 | 46 | if jwtpath != "" { 47 | if _, err := os.Stat(jwtpath); os.IsNotExist(err) { 48 | return fmt.Errorf("the provided jwt file does not exist: %s", jwtpath) 49 | } 50 | } 51 | 52 | if caOverride != "" { 53 | if _, err := os.Stat(caOverride); os.IsNotExist(err) { 54 | return fmt.Errorf("the provided ca file does not exist: %s", caOverride) 55 | } 56 | } 57 | 58 | if strings.TrimSpace(outpath) == strings.TrimSpace(jwtpath) { 59 | return fmt.Errorf("the output path must not be the same as the jwt path") 60 | } 61 | 62 | tokenStr, _ := ioutil.ReadFile(jwtpath) 63 | 64 | pfxlog.Logger().Debugf("jwt to parse: %s", tokenStr) 65 | tkn, _, err := enroll.ParseToken(string(tokenStr)) 66 | 67 | if err != nil { 68 | return fmt.Errorf("failed to parse JWT: %s", err.Error()) 69 | } 70 | 71 | flags := enroll.EnrollmentFlags{ 72 | CertFile: certPath, 73 | KeyFile: keyPath, 74 | KeyAlg: keyAlg, 75 | Token: tkn, 76 | IDName: idname, 77 | AdditionalCAs: caOverride, 78 | } 79 | 80 | conf, err := enroll.Enroll(flags) 81 | if err != nil { 82 | return fmt.Errorf("failed to enroll: %v", err) 83 | } 84 | 85 | output, err := os.Create(outpath) 86 | if err != nil { 87 | return fmt.Errorf("failed to open file '%s': %s", outpath, err.Error()) 88 | } 89 | defer func() { _ = output.Close() }() 90 | 91 | enc := json.NewEncoder(output) 92 | enc.SetEscapeHTML(false) 93 | encErr := enc.Encode(&conf) 94 | 95 | if encErr == nil { 96 | pfxlog.Logger().Infof("enrolled successfully. identity file written to: %s", outpath) 97 | return nil 98 | } else { 99 | return fmt.Errorf("enrollment successful but the identity file was not able to be written to: %s [%s]", outpath, encErr) 100 | } 101 | } 102 | 103 | func outPathFromJwt(jwt string) (string, error) { 104 | outFlag := "out" 105 | if strings.HasSuffix(jwt, ".jwt") { 106 | return jwt[:len(jwt)-len(".jwt")] + ".json", nil 107 | } else if strings.HasSuffix(jwt, ".json") { 108 | //ugh - so that makes things a bit uglier but ok fine. we'll return an error in this situation 109 | return "", errors.Errorf("unexpected configuration. cannot infer '%s' flag if the jwt file "+ 110 | "ends in .json. rename jwt file or provide the '%s' flag", outFlag, outFlag) 111 | } else { 112 | //doesn't end with .jwt - so just slap a .json on the end and call it a day 113 | return jwt + ".json", nil 114 | } 115 | } 116 | 117 | // enrollCmd represents the enroll command 118 | var enrollCmd = &cobra.Command{ 119 | Use: "enroll", 120 | Short: "Enroll an identity", 121 | Long: `This command enrolls an identity with an input jwt file and outputs a json identity file.`, 122 | Run: func(cmd *cobra.Command, args []string) { 123 | jflag, _ := cmd.Flags().GetString("jwt") 124 | oflag, _ := cmd.Flags().GetString("out") 125 | 126 | if len(jflag) > 0 { 127 | err := processEnrollment(jflag, oflag) 128 | if err != nil { 129 | logrus.WithError(err).Error("Error enrolling") 130 | os.Exit(1) 131 | } 132 | os.Exit(0) 133 | } else { 134 | fmt.Fprintf(os.Stderr, "'enroll command' requires -j,--jwt \n") 135 | os.Exit(2) 136 | } 137 | 138 | }, 139 | } 140 | 141 | func init() { 142 | rootCmd.AddCommand(enrollCmd) 143 | enrollCmd.Flags().StringP("jwt", "j", "", "Name/Location of jwt file") 144 | enrollCmd.Flags().StringP("out", "o", "", "Optional: Name/Location of output identity file") 145 | } 146 | -------------------------------------------------------------------------------- /example/zping/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | package main 17 | 18 | func main() { 19 | Execute() 20 | } 21 | -------------------------------------------------------------------------------- /example/zping/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/ad526d40ec0833d7fc589790e97243b6131c4bf8/example/zping/network.png -------------------------------------------------------------------------------- /example/zping/root.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | 23 | "github.com/spf13/cobra" 24 | 25 | "github.com/spf13/viper" 26 | ) 27 | 28 | var cfgFile string 29 | 30 | // rootCmd represents the base command when called without any subcommands 31 | var rootCmd = &cobra.Command{ 32 | Use: "zping", 33 | Short: "Latency Diagnostic tool for ziti", 34 | Long: `zping replaces the function of icmp ping tool in a ziti network. 35 | It provides an end to end latency measurement between any two ziti identities in 36 | a ziti network and like icmp ping will provide the following metrics upon completion 37 | of the ping session: min, max and mean latency and standard deviation as well as % loss. 38 | zping uses the addressable terminator function of ziti to direct ping requests to 39 | specific ziti identities.`, 40 | } 41 | 42 | // Execute adds all child commands to the root command and sets flags appropriately. 43 | // This is called by main.main(). It only needs to happen once to the rootCmd. 44 | func Execute() { 45 | cobra.CheckErr(rootCmd.Execute()) 46 | } 47 | 48 | func init() { 49 | cobra.OnInitialize(initConfig) 50 | } 51 | 52 | // initConfig reads in config file and ENV variables if set. 53 | func initConfig() { 54 | 55 | if cfgFile != "" { 56 | // Use config file from the flag. 57 | viper.SetConfigFile(cfgFile) 58 | } else { 59 | // Find home directory. 60 | home, err := os.UserHomeDir() 61 | cobra.CheckErr(err) 62 | 63 | // Search config in home directory with name ".zping" (without extension). 64 | viper.AddConfigPath(home) 65 | viper.SetConfigType("yaml") 66 | viper.SetConfigName(".zping") 67 | } 68 | 69 | viper.AutomaticEnv() // read in environment variables that match 70 | 71 | // If a config file is found, read it in. 72 | if err := viper.ReadInConfig(); err == nil { 73 | fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /example/zping/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "github.com/michaelquigley/pfxlog" 22 | "github.com/openziti/sdk-golang/ziti" 23 | "github.com/openziti/sdk-golang/ziti/edge" 24 | "github.com/sirupsen/logrus" 25 | "net" 26 | "os" 27 | "time" 28 | 29 | "github.com/spf13/cobra" 30 | ) 31 | 32 | func handlePing(conn net.Conn) { 33 | for { 34 | buf := make([]byte, 1500) 35 | n, err := conn.Read(buf) 36 | if err != nil { 37 | _ = conn.Close() 38 | return 39 | } 40 | msg := buf[:n] 41 | if _, err := conn.Write(msg); err != nil { 42 | logrus.WithError(err).Error("failed to write. closing connection") 43 | _ = conn.Close() 44 | } 45 | } 46 | } 47 | 48 | // serverCmd represents the server command 49 | var serverCmd = &cobra.Command{ 50 | Use: "server", 51 | Short: "zping server command", 52 | Long: `This command runs zping in server mode which responds to ziti probe 53 | messages which are sent to it's associated ziti identity by zping clients`, 54 | Run: func(cmd *cobra.Command, args []string) { 55 | sflag, _ := cmd.Flags().GetString("service") 56 | cflag, _ := cmd.Flags().GetString("config") 57 | var service string 58 | if len(sflag) > 0 { 59 | service = sflag 60 | } else { 61 | service = "ziti-ping" 62 | } 63 | logger := pfxlog.Logger() 64 | options := ziti.ListenOptions{ 65 | ConnectTimeout: 10 * time.Second, 66 | MaxConnections: 3, 67 | BindUsingEdgeIdentity: true, 68 | } 69 | logger.Infof("binding service %v\n", service) 70 | var listener edge.Listener 71 | if len(cflag) > 0 { 72 | file := cflag 73 | configFile, err := ziti.NewConfigFromFile(file) 74 | if err != nil { 75 | logrus.WithError(err).Error("Error loading config file") 76 | os.Exit(1) 77 | } 78 | context, err := ziti.NewContext(configFile) 79 | 80 | if err != nil { 81 | panic(err) 82 | } 83 | 84 | identity, err := context.GetCurrentIdentity() 85 | if err != nil { 86 | logrus.WithError(err).Error("Error resolving local Identity") 87 | os.Exit(1) 88 | } 89 | fmt.Printf("\n%+v now serving\n\n", identity.Name) 90 | listener, err = context.ListenWithOptions(service, &options) 91 | if err != nil { 92 | logrus.WithError(err).Error("Error Binding Service") 93 | os.Exit(1) 94 | } 95 | } else { 96 | panic("a config file is required") 97 | } 98 | 99 | for { 100 | conn, err := listener.Accept() 101 | if err != nil { 102 | logrus.WithError(err).Error("Problem accepting connection, sleeping for 5 Seconds") 103 | time.Sleep(time.Duration(5) * time.Second) 104 | } 105 | logger.Infof("new connection") 106 | fmt.Println() 107 | go handlePing(conn) 108 | } 109 | }, 110 | } 111 | 112 | func init() { 113 | rootCmd.AddCommand(serverCmd) 114 | serverCmd.Flags().StringP("service", "s", "ziti-ping", "Name of Service") 115 | serverCmd.Flags().StringP("config", "c", "", "Name of config file") 116 | } 117 | -------------------------------------------------------------------------------- /exercises/README.md: -------------------------------------------------------------------------------- 1 | # Exercises 2 | 3 | This directory is meant for any code samples or documentation that would help one learn more about how to use the Ziti SDK for go. 4 | 5 | The first exercise shows code before and after zitifying a simple http server. 6 | -------------------------------------------------------------------------------- /exercises/http/README.md: -------------------------------------------------------------------------------- 1 | # HTTP 2 | 3 | This exercise takes a simple server listening on a port. The example is contrived and simple on purpose. When deploying 4 | locally the basic setup would look like this: 5 | 6 | ![image](./simple-example.png) 7 | 8 | When deploying a server like this out to the internet, for example using AWS, that same basic overview would 9 | probably look more like this simplified diagram below. Notice that port 8090 is now open through the firewall. The 10 | server is also listening on port 8090. 11 | 12 | ![image](./simple-cloud.png) 13 | 14 | With Ziti you can eliminate the need for listening ports, the need for firewall holes in you deployments entirely. 15 | This is what the same example looks like using Ziti. Notice no inbound firewall ports needed on either side. No 16 | listening port at all in the zitified example, just a Ziti service which is "bound" to the server identity 17 | 18 | ![image](./simple-zitified-example.png) 19 | -------------------------------------------------------------------------------- /exercises/http/client/before/simple-client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | func main() { 10 | target := "localhost:8090" 11 | helloUrl := fmt.Sprintf("http://%s/hello", target) 12 | httpClient := http.Client{} 13 | resp, e := httpClient.Get(helloUrl) 14 | if e != nil { 15 | panic(e) 16 | } 17 | body, _ := io.ReadAll(resp.Body) 18 | fmt.Println("Hello response:", string(body)) 19 | 20 | a := 1 21 | b := 2 22 | addUrl := fmt.Sprintf("http://%s/add?a=%d&b=%d", target, a, b) 23 | resp, e = httpClient.Get(addUrl) 24 | if e != nil { 25 | panic(e) 26 | } 27 | body, _ = io.ReadAll(resp.Body) 28 | fmt.Println("Add Result:", string(body)) 29 | } 30 | -------------------------------------------------------------------------------- /exercises/http/client/zitified/simple-client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/openziti/sdk-golang/ziti" 7 | "io" 8 | "net" 9 | "net/http" 10 | "os" 11 | "strings" 12 | ) 13 | 14 | func main() { 15 | target := os.Args[2] 16 | helloUrl := fmt.Sprintf("http://%s/hello", target) 17 | httpClient := createZitifiedHttpClient(os.Args[1]) 18 | resp, e := httpClient.Get(helloUrl) 19 | if e != nil { 20 | panic(e) 21 | } 22 | body, _ := io.ReadAll(resp.Body) 23 | fmt.Println("Hello response:", string(body)) 24 | 25 | a := 1 26 | b := 2 27 | addUrl := fmt.Sprintf("http://%s/add?a=%d&b=%d", target, a, b) 28 | resp, e = httpClient.Get(addUrl) 29 | if e != nil { 30 | panic(e) 31 | } 32 | body, _ = io.ReadAll(resp.Body) 33 | fmt.Println("Add Result:", string(body)) 34 | } 35 | 36 | var zitiContext ziti.Context 37 | 38 | func Dial(_ context.Context, _ string, addr string) (net.Conn, error) { 39 | service := strings.Split(addr, ":")[0] // will always get passed host:port 40 | return zitiContext.Dial(service) 41 | } 42 | func createZitifiedHttpClient(idFile string) http.Client { 43 | cfg, err := ziti.NewConfigFromFile(idFile) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | zitiContext, err = ziti.NewContext(cfg) 49 | 50 | if err != nil { 51 | panic(err) 52 | } 53 | 54 | zitiTransport := http.DefaultTransport.(*http.Transport).Clone() // copy default transport 55 | zitiTransport.DialContext = Dial //zitiDialContext.Dial 56 | return http.Client{Transport: zitiTransport} 57 | } 58 | -------------------------------------------------------------------------------- /exercises/http/server/before/simple-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "os" 7 | "strconv" 8 | ) 9 | 10 | func main() { 11 | http.HandleFunc("/hello", hello) 12 | http.HandleFunc("/add", add) 13 | if err := http.ListenAndServe(":8090", nil); err != nil { 14 | panic(err) 15 | } 16 | } 17 | 18 | func hello(w http.ResponseWriter, req *http.Request) { 19 | host, _ := os.Hostname() 20 | fmt.Fprintf(w, "zitified hello from %s", host) 21 | } 22 | 23 | func add(w http.ResponseWriter, req *http.Request) { 24 | a, _ := strconv.Atoi(req.URL.Query().Get("a")) 25 | b, _ := strconv.Atoi(req.URL.Query().Get("b")) 26 | c := a + b 27 | fmt.Fprintf(w, "a+b=%d+%d=%d", a, b, c) 28 | } 29 | -------------------------------------------------------------------------------- /exercises/http/server/zitified/simple-server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/openziti/sdk-golang/ziti" 6 | "net" 7 | "net/http" 8 | "os" 9 | "strconv" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | http.HandleFunc("/hello", hello) 15 | http.HandleFunc("/add", add) 16 | if err := http.Serve(createZitiListener(), nil); err != nil { 17 | panic(err) 18 | } 19 | } 20 | 21 | func hello(w http.ResponseWriter, req *http.Request) { 22 | host, _ := os.Hostname() 23 | fmt.Fprintf(w, "zitified hello from %s", host) 24 | } 25 | 26 | func add(w http.ResponseWriter, req *http.Request) { 27 | a, _ := strconv.Atoi(req.URL.Query().Get("a")) 28 | b, _ := strconv.Atoi(req.URL.Query().Get("b")) 29 | c := a + b 30 | fmt.Fprintf(w, "zitified a+b=%d+%d=%d", a, b, c) 31 | } 32 | 33 | func createZitiListener() net.Listener { 34 | cfg, err := ziti.NewConfigFromFile(os.Args[1]) 35 | if err != nil { 36 | panic(err) 37 | } 38 | options := ziti.ListenOptions{ 39 | ConnectTimeout: 5 * time.Minute, 40 | } 41 | ctx, err := ziti.NewContext(cfg) 42 | 43 | if err != nil { 44 | panic(err) 45 | } 46 | 47 | listener, err := ctx.ListenWithOptions(os.Args[2], &options) 48 | if err != nil { 49 | fmt.Printf("Error binding service %+v\n", err) 50 | panic(err) 51 | } 52 | return listener 53 | } 54 | -------------------------------------------------------------------------------- /exercises/http/simple-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/ad526d40ec0833d7fc589790e97243b6131c4bf8/exercises/http/simple-cloud.png -------------------------------------------------------------------------------- /exercises/http/simple-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/ad526d40ec0833d7fc589790e97243b6131c4bf8/exercises/http/simple-example.png -------------------------------------------------------------------------------- /exercises/http/simple-zitified-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/ad526d40ec0833d7fc589790e97243b6131c4bf8/exercises/http/simple-zitified-example.png -------------------------------------------------------------------------------- /expected.licenses: -------------------------------------------------------------------------------- 1 | Apache-2.0 2 | BSD-2-Clause 3 | BSD-2-Clause-FreeBSD 4 | BSD-3-Clause 5 | MIT 6 | Unlicense 7 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openziti/sdk-golang 2 | 3 | go 1.23.7 4 | 5 | toolchain go1.24.1 6 | 7 | require ( 8 | github.com/Jeffail/gabs v1.4.0 9 | github.com/cenkalti/backoff/v4 v4.3.0 10 | github.com/emirpasic/gods v1.18.1 11 | github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa 12 | github.com/go-openapi/runtime v0.28.0 13 | github.com/go-openapi/strfmt v0.23.0 14 | github.com/go-resty/resty/v2 v2.16.5 15 | github.com/golang-jwt/jwt/v5 v5.2.2 16 | github.com/google/uuid v1.6.0 17 | github.com/kataras/go-events v0.0.3 18 | github.com/michaelquigley/pfxlog v0.6.10 19 | github.com/mitchellh/go-ps v1.0.0 20 | github.com/mitchellh/mapstructure v1.5.0 21 | github.com/openziti/channel/v4 v4.2.0 22 | github.com/openziti/edge-api v0.26.45 23 | github.com/openziti/foundation/v2 v2.0.63 24 | github.com/openziti/identity v1.0.101 25 | github.com/openziti/metrics v1.4.1 26 | github.com/openziti/secretstream v0.1.34 27 | github.com/openziti/transport/v2 v2.0.171 28 | github.com/orcaman/concurrent-map/v2 v2.0.1 29 | github.com/pkg/errors v0.9.1 30 | github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 31 | github.com/shirou/gopsutil/v3 v3.24.5 32 | github.com/sirupsen/logrus v1.9.3 33 | github.com/stretchr/testify v1.10.0 34 | github.com/zitadel/oidc/v3 v3.39.0 35 | go.mozilla.org/pkcs7 v0.9.0 36 | golang.org/x/oauth2 v0.30.0 37 | golang.org/x/sys v0.33.0 38 | google.golang.org/protobuf v1.36.6 39 | ) 40 | 41 | require ( 42 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 43 | github.com/davecgh/go-spew v1.1.1 // indirect 44 | github.com/fsnotify/fsnotify v1.8.0 // indirect 45 | github.com/go-jose/go-jose/v4 v4.0.5 // indirect 46 | github.com/go-logr/logr v1.4.2 // indirect 47 | github.com/go-logr/stdr v1.2.2 // indirect 48 | github.com/go-ole/go-ole v1.2.6 // indirect 49 | github.com/go-openapi/analysis v0.23.0 // indirect 50 | github.com/go-openapi/errors v0.22.0 // indirect 51 | github.com/go-openapi/jsonpointer v0.21.0 // indirect 52 | github.com/go-openapi/jsonreference v0.21.0 // indirect 53 | github.com/go-openapi/loads v0.22.0 // indirect 54 | github.com/go-openapi/spec v0.21.0 // indirect 55 | github.com/go-openapi/swag v0.23.0 // indirect 56 | github.com/go-openapi/validate v0.24.0 // indirect 57 | github.com/gorilla/mux v1.8.1 // indirect 58 | github.com/gorilla/securecookie v1.1.2 // indirect 59 | github.com/gorilla/websocket v1.5.3 // indirect 60 | github.com/josharian/intern v1.0.0 // indirect 61 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 62 | github.com/mailru/easyjson v0.7.7 // indirect 63 | github.com/mattn/go-colorable v0.1.14 // indirect 64 | github.com/mattn/go-isatty v0.0.20 // indirect 65 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 66 | github.com/miekg/pkcs11 v1.1.1 // indirect 67 | github.com/muhlemmer/gu v0.3.1 // indirect 68 | github.com/oklog/ulid v1.3.1 // indirect 69 | github.com/opentracing/opentracing-go v1.2.0 // indirect 70 | github.com/parallaxsecond/parsec-client-go v0.0.0-20221025095442-f0a77d263cf9 // indirect 71 | github.com/pmezard/go-difflib v1.0.0 // indirect 72 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 73 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 74 | github.com/speps/go-hashids v2.0.0+incompatible // indirect 75 | github.com/tklauser/go-sysconf v0.3.12 // indirect 76 | github.com/tklauser/numcpus v0.6.1 // indirect 77 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 78 | github.com/zitadel/logging v0.6.2 // indirect 79 | github.com/zitadel/schema v1.3.1 // indirect 80 | go.mongodb.org/mongo-driver v1.17.0 // indirect 81 | go.opentelemetry.io/otel v1.29.0 // indirect 82 | go.opentelemetry.io/otel/metric v1.29.0 // indirect 83 | go.opentelemetry.io/otel/trace v1.29.0 // indirect 84 | golang.org/x/crypto v0.38.0 // indirect 85 | golang.org/x/net v0.40.0 // indirect 86 | golang.org/x/sync v0.14.0 // indirect 87 | golang.org/x/term v0.32.0 // indirect 88 | golang.org/x/text v0.25.0 // indirect 89 | gopkg.in/yaml.v3 v3.0.1 // indirect 90 | nhooyr.io/websocket v1.8.17 // indirect 91 | ) 92 | -------------------------------------------------------------------------------- /http_transport.go: -------------------------------------------------------------------------------- 1 | package sdk_golang 2 | 3 | import ( 4 | "context" 5 | "crypto/tls" 6 | "net" 7 | "net/http" 8 | "strings" 9 | 10 | "github.com/openziti/sdk-golang/ziti" 11 | "github.com/openziti/sdk-golang/ziti/edge" 12 | cmap "github.com/orcaman/concurrent-map/v2" 13 | ) 14 | 15 | // NewHttpClient returns a http.Client that can be used exactly as any other http.Client but will route requests 16 | // over a Ziti network using the host name as the Ziti service name. Supplying a tlsConfig is possible to connect 17 | // to HTTPS services, but for it to be successful, the Ziti service name MUST be in the servers URI SANs. 18 | func NewHttpClient(ctx ziti.Context, tlsConfig *tls.Config) *http.Client { 19 | return &http.Client{ 20 | Transport: NewZitiTransport(ctx, tlsConfig), 21 | } 22 | } 23 | 24 | // ZitiTransport wraps the default http.RoundTripper implementation with Ziti edge.Conn pooling 25 | type ZitiTransport struct { 26 | http.Transport 27 | connByAddr cmap.ConcurrentMap[string, edge.Conn] 28 | Context ziti.Context 29 | TlsConfig *tls.Config 30 | } 31 | 32 | // NewZitiTransport returns a new http.Transport that routes HTTP requests and response over a 33 | // Ziti network. 34 | func NewZitiTransport(ctx ziti.Context, clientTlsConfig *tls.Config) *ZitiTransport { 35 | zitiTransport := &ZitiTransport{ 36 | connByAddr: cmap.New[edge.Conn](), 37 | TlsConfig: clientTlsConfig, 38 | Context: ctx, 39 | } 40 | 41 | zitiTransport.Transport = http.Transport{ 42 | DialContext: zitiTransport.DialContext, 43 | DialTLSContext: zitiTransport.DialTLSContext, 44 | } 45 | 46 | return zitiTransport 47 | } 48 | 49 | // urlToServiceName removes ports from host names that internal standard GoLang capabilities may have added. 50 | func urlToServiceName(addr string) string { 51 | return strings.Split(addr, ":")[0] 52 | } 53 | 54 | // getConn returns an edge.Conn that can act as a net.Conn, but is pooled by service name. 55 | func (transport *ZitiTransport) getConn(addr string) (edge.Conn, error) { 56 | var err error 57 | edgeConn := transport.connByAddr.Upsert(addr, nil, func(_ bool, existingConn edge.Conn, _ edge.Conn) edge.Conn { 58 | if existingConn == nil || existingConn.IsClosed() { 59 | var newConn edge.Conn 60 | 61 | serviceName := urlToServiceName(addr) 62 | 63 | if err != nil { 64 | return nil 65 | } 66 | 67 | newConn, err = transport.Context.Dial(serviceName) 68 | 69 | return newConn 70 | } 71 | 72 | return existingConn 73 | }) 74 | 75 | return edgeConn, err 76 | } 77 | 78 | func (transport *ZitiTransport) DialContext(ctx context.Context, network, addr string) (net.Conn, error) { 79 | edgeConn, err := transport.getConn(addr) 80 | 81 | return edgeConn, err 82 | } 83 | 84 | func (transport *ZitiTransport) DialTLSContext(ctx context.Context, network, addr string) (net.Conn, error) { 85 | edgeConn, err := transport.getConn(addr) 86 | 87 | if err != nil { 88 | return nil, err 89 | } 90 | tlsConn := tls.Client(edgeConn, transport.TlsConfig) 91 | 92 | if err := tlsConn.Handshake(); err != nil { 93 | if edgeConn != nil { 94 | _ = edgeConn.Close() 95 | } 96 | return nil, err 97 | } 98 | 99 | return edgeConn, err 100 | } 101 | -------------------------------------------------------------------------------- /inspect/inspect.go: -------------------------------------------------------------------------------- 1 | package inspect 2 | 3 | type SdkInspectResponse struct { 4 | Errors []string `json:"errors"` 5 | Success bool `json:"success"` 6 | Values map[string]any `json:"values"` 7 | } 8 | -------------------------------------------------------------------------------- /license-check.sh: -------------------------------------------------------------------------------- 1 | printf "Installing go-licenses" 2 | go install github.com/google/go-licenses@latest 3 | 4 | printf "\nGenerating license report" 5 | $(go env GOPATH)/bin/go-licenses report ./... > /tmp/sdk.licenses 6 | 7 | printf "\nGenerating set of unique licenses" 8 | cat /tmp/sdk.licenses | cut -d ',' -f 3 | sort | uniq > /tmp/sdk.licenses.unique 9 | 10 | printf "\nChecking Licenses\n" 11 | diff expected.licenses /tmp/sdk.licenses.unique 12 | -------------------------------------------------------------------------------- /pb/edge_client_pb/README.md: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | 3 | 1. Install the protoc binary from: https://github.com/protocolbuffers/protobuf/releases 4 | 2. Install the protoc plugin for Go ```go install google.golang.org/protobuf/cmd/protoc-gen-go@latest``` 5 | 3. Ensure ```protoc``` is on your path. 6 | 4. Ensure your Go bin directory is on your path 7 | 8 | 9 | # Generate Go Code 10 | 11 | Two options, run the command manually or use `go generate` 12 | 13 | ## Go Generate 14 | 15 | 1. Navigate to the root project directory `sdk-golang` 16 | 2. run `go generate ./pb/edge_client_pb/...` or `go generate /pb/edge_client_pb/...` 17 | 18 | Note: Running a naked `go generate` will trigger all `go:generate` tags in the project, which you most likely do not want 19 | 20 | ## Manually 21 | 22 | 1. Navigate to the project root 23 | 2. Run: ```protoc -I ./pb/ ./pb/edge_client_pb/edge_client.proto --go_out=./pb/edge_client_pb``` 24 | -------------------------------------------------------------------------------- /pb/edge_client_pb/edge_client.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package ziti.edge_client.pb; 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | option go_package = "github.com/openziti/sdk-golang/pb/edge_client_pb"; 8 | option java_package = "org.openziti.edge.proto"; 9 | 10 | enum ContentType { 11 | Hello = 0; 12 | Ping = 1; 13 | Result = 2; 14 | Latency = 3; 15 | 16 | // use the same types as xgress uses on links 17 | XgPayloadType = 1100; 18 | XgAcknowledgementType = 1101; 19 | XgControlType = 1102; 20 | XgCloseType = 1103; 21 | 22 | ConnectType = 60783; 23 | StateConnectedType = 60784; 24 | StateClosedType = 60785; 25 | DataType = 60786; 26 | DialType = 60787; 27 | DialSuccessType = 60788; 28 | DialFailedType = 60789; 29 | BindType = 60790; 30 | UnbindType = 60791; 31 | StateSessionEndedType = 60792; 32 | ProbeType = 60793; 33 | UpdateBindType = 60794; 34 | HealthEventType = 60795; 35 | TraceRouteType = 60796; 36 | TraceRouteResponseType = 60797; 37 | ConnInspectRequest = 60798; 38 | ConnInspectResponse = 60799; 39 | BindSuccess = 60800; 40 | UpdateTokenSuccessType = 60801; 41 | UpdateTokenFailureType = 60802; 42 | UpdateTokenType = 60803; 43 | 44 | InspectRequest = 60804; 45 | InspectResponse = 60805; 46 | 47 | PostureResponseType = 10800; 48 | PostureResponseSuccessType = 10801; 49 | } 50 | 51 | enum HeaderId { 52 | ZER0 = 0; 53 | UUID = 128; 54 | ConnId = 1000; 55 | Seq = 1001; 56 | SessionToken = 1002; 57 | PublicKey = 1003; 58 | Cost = 1004; 59 | Precedence = 1005; 60 | TerminatorIdentity = 1006; 61 | TerminatorIdentitySecret = 1007; 62 | CallerId = 1008; 63 | CryptoMethod = 1009; 64 | Flags = 1010; 65 | AppData = 1011; 66 | RouterProvidedConnId = 1012; 67 | HealthStatus = 1013; 68 | ErrorCode = 1014; 69 | Timestamp = 1015; 70 | TraceHopCount = 1016; 71 | TraceHopType = 1017; 72 | TraceHopId = 1018; 73 | TraceSourceRequestId = 1019; 74 | TraceError = 1020; 75 | ListenerId = 1021; 76 | ConnType = 1022; 77 | SupportsInspect = 1023; 78 | SupportsBindSuccess = 1024; 79 | ConnectionMarker = 1025; 80 | CircuitId = 1026; 81 | StickinessToken = 1027; 82 | UseXgressToSdk = 1028; 83 | XgressCtrlId = 1029; 84 | XgressAddress = 1030; 85 | InspectRequestedValues = 1031; 86 | } 87 | 88 | enum Error { 89 | OK = 0; 90 | Internal = 1; 91 | InvalidApiSession = 2; 92 | InvalidSession = 3; 93 | WrongSessionType = 4; 94 | InvalidEdgeRouterForSession = 5; 95 | InvalidService = 6; 96 | TunnelingNotEnabled = 7; 97 | InvalidTerminator = 8; 98 | InvalidPrecedence = 9; 99 | InvalidCost = 10; 100 | EncryptionDataMissing = 11; 101 | } 102 | 103 | enum PrecedenceValue { 104 | Default = 0; 105 | Required = 1; 106 | Failed = 2; 107 | } 108 | 109 | enum Flag { 110 | ZERO = 0; 111 | 112 | // FIN is an edge payload flag used to signal communication ends 113 | FIN = 1; 114 | // TRACE_UUID indicates that peer will send data messages with specially constructed UUID headers 115 | TRACE_UUID = 2; 116 | // MULTIPART indicates that peer can accept multipart data messages 117 | MULTIPART = 4; 118 | // STREAM indicates connection with stream semantics 119 | // this allows consolidation of payloads to lower overhead 120 | STREAM = 8; 121 | // MULTIPART_MSG set on data message with multiple payloads 122 | MULTIPART_MSG = 16; 123 | } 124 | 125 | message PostureResponses { 126 | repeated PostureResponse responses = 1; 127 | } 128 | 129 | message PostureResponse { 130 | oneof Type { 131 | Macs macs = 1; 132 | OperatingSystem os = 2; 133 | ProcessList processList = 3; 134 | Domain domain = 4; 135 | Woken woken = 5; 136 | Unlocked unlocked = 6; 137 | SdkInfo sdkInfo = 7; 138 | }; 139 | 140 | 141 | message Macs { 142 | repeated string addresses = 1; 143 | } 144 | 145 | message OperatingSystem { 146 | string type = 1; 147 | string version = 2; 148 | string build = 3; 149 | } 150 | 151 | message Domain { 152 | string name = 1; 153 | } 154 | 155 | message Process { 156 | string path = 1; 157 | bool isRunning = 2; 158 | string hash = 3; 159 | repeated string signerFingerprints = 4; 160 | } 161 | 162 | message ProcessList { 163 | repeated Process processes = 1; 164 | } 165 | 166 | message Woken { 167 | google.protobuf.Timestamp Time = 1; 168 | } 169 | 170 | message Unlocked { 171 | google.protobuf.Timestamp Time = 1; 172 | } 173 | 174 | message SdkInfo { 175 | string appId = 1; 176 | string appVersion = 2; 177 | string branch = 3; 178 | string revision = 4; 179 | string type = 5; 180 | string version = 6; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /pb/edge_client_pb/generate.go: -------------------------------------------------------------------------------- 1 | //go:generate protoc -I ./ ./edge_client.proto --go_out=paths=source_relative:./ 2 | 3 | package edge_client_pb 4 | 5 | // Here to provide the go:generate line above 6 | -------------------------------------------------------------------------------- /sdk-version/sdk-version.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/openziti/sdk-golang/ziti/sdkinfo" 6 | ) 7 | 8 | func main() { 9 | _, sdkInfo := sdkinfo.GetSdkInfo() 10 | fmt.Printf("%s", sdkInfo.Version) 11 | } 12 | -------------------------------------------------------------------------------- /version: -------------------------------------------------------------------------------- 1 | 1.1 2 | -------------------------------------------------------------------------------- /xgress/circuit_inspections.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package xgress 18 | 19 | type CircuitInspectDetail struct { 20 | CircuitId string `json:"circuitId"` 21 | Forwards map[string]string `json:"forwards"` 22 | XgressDetails map[string]*InspectDetail `json:"xgressDetails"` 23 | RelatedEntities map[string]map[string]any `json:"relatedEntities"` 24 | Errors []string `json:"errors"` 25 | includeGoroutines bool 26 | } 27 | 28 | func (self *CircuitInspectDetail) SetIncludeGoroutines(includeGoroutines bool) { 29 | self.includeGoroutines = includeGoroutines 30 | } 31 | 32 | func (self *CircuitInspectDetail) IncludeGoroutines() bool { 33 | return self.includeGoroutines 34 | } 35 | 36 | func (self *CircuitInspectDetail) AddXgressDetail(xgressDetail *InspectDetail) { 37 | self.XgressDetails[xgressDetail.Address] = xgressDetail 38 | } 39 | 40 | func (self *CircuitInspectDetail) AddRelatedEntity(entityType string, id string, detail any) { 41 | if self.RelatedEntities == nil { 42 | self.RelatedEntities = make(map[string]map[string]any) 43 | } 44 | if self.RelatedEntities[entityType] == nil { 45 | self.RelatedEntities[entityType] = make(map[string]any) 46 | } 47 | self.RelatedEntities[entityType][id] = detail 48 | } 49 | 50 | func (self *CircuitInspectDetail) AddError(err error) { 51 | self.Errors = append(self.Errors, err.Error()) 52 | } 53 | 54 | type InspectDetail struct { 55 | Address string `json:"address"` 56 | Originator string `json:"originator"` 57 | TimeSinceLastLinkRx string `json:"timeSinceLastLinkRx"` 58 | SendBufferDetail *SendBufferDetail `json:"sendBufferDetail"` 59 | RecvBufferDetail *RecvBufferDetail `json:"recvBufferDetail"` 60 | XgressPointer string `json:"xgressPointer"` 61 | LinkSendBufferPointer string `json:"linkSendBufferPointer"` 62 | Goroutines []string `json:"goroutines"` 63 | Sequence uint64 `json:"sequence"` 64 | Flags string `json:"flags"` 65 | } 66 | 67 | type SendBufferDetail struct { 68 | WindowSize uint32 `json:"windowSize"` 69 | LinkSendBufferSize uint32 `json:"linkSendBufferSize"` 70 | LinkRecvBufferSize uint32 `json:"linkRecvBufferSize"` 71 | Accumulator uint32 `json:"accumulator"` 72 | SuccessfulAcks uint32 `json:"successfulAcks"` 73 | DuplicateAcks uint32 `json:"duplicateAcks"` 74 | Retransmits uint32 `json:"retransmits"` 75 | Closed bool `json:"closed"` 76 | BlockedByLocalWindow bool `json:"blockedByLocalWindow"` 77 | BlockedByRemoteWindow bool `json:"blockedByRemoteWindow"` 78 | RetxScale float64 `json:"retxScale"` 79 | RetxThreshold uint32 `json:"retxThreshold"` 80 | TimeSinceLastRetx string `json:"timeSinceLastRetx"` 81 | CloseWhenEmpty bool `json:"closeWhenEmpty"` 82 | AcquiredSafely bool `json:"acquiredSafely"` 83 | } 84 | 85 | type RecvBufferDetail struct { 86 | Size uint32 `json:"size"` 87 | PayloadCount uint32 `json:"payloadCount"` 88 | LastSizeSent uint32 `json:"lastSizeSent"` 89 | Sequence int32 `json:"sequence"` 90 | MaxSequence int32 `json:"maxSequence"` 91 | NextPayload string `json:"nextPayload"` 92 | AcquiredSafely bool `json:"acquiredSafely"` 93 | } 94 | 95 | type CircuitsDetail struct { 96 | Circuits map[string]*CircuitDetail `json:"circuits"` 97 | } 98 | 99 | type CircuitDetail struct { 100 | CircuitId string `json:"circuitId"` 101 | ConnId uint32 `json:"connId"` 102 | Address string `json:"address"` 103 | Originator string `json:"originator"` 104 | IsXgress bool `json:"isXgress"` 105 | } 106 | -------------------------------------------------------------------------------- /xgress/decoder.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package xgress 18 | 19 | import ( 20 | "fmt" 21 | "github.com/michaelquigley/pfxlog" 22 | "github.com/openziti/channel/v4" 23 | ) 24 | 25 | type Decoder struct{} 26 | 27 | const DECODER = "data" 28 | 29 | func (d Decoder) Decode(msg *channel.Message) ([]byte, bool) { 30 | switch msg.ContentType { 31 | case int32(ContentTypePayloadType): 32 | if payload, err := UnmarshallPayload(msg); err == nil { 33 | return DecodePayload(payload) 34 | } else { 35 | pfxlog.Logger().WithError(err).Error("unexpected error unmarshalling payload msg") 36 | } 37 | 38 | case int32(ContentTypeAcknowledgementType): 39 | if ack, err := UnmarshallAcknowledgement(msg); err == nil { 40 | meta := channel.NewTraceMessageDecode(DECODER, "Acknowledgement") 41 | meta["circuitId"] = ack.CircuitId 42 | meta["sequence"] = fmt.Sprintf("len(%d)", len(ack.Sequence)) 43 | switch ack.GetOriginator() { 44 | case Initiator: 45 | meta["originator"] = "i" 46 | case Terminator: 47 | meta["originator"] = "e" 48 | } 49 | 50 | data, err := meta.MarshalTraceMessageDecode() 51 | if err != nil { 52 | return nil, true 53 | } 54 | 55 | return data, true 56 | 57 | } else { 58 | pfxlog.Logger().WithError(err).Error("unexpected error unmarshalling ack msg") 59 | } 60 | case int32(ContentTypeControlType): 61 | if control, err := UnmarshallControl(msg); err == nil { 62 | meta := channel.NewTraceMessageDecode(DECODER, "Control") 63 | meta["circuitId"] = control.CircuitId 64 | meta["type"] = control.Type.String() 65 | if control.Type == ControlTypeTraceRoute || control.Type == ControlTypeTraceRouteResponse { 66 | if ts, found := msg.GetUint64Header(ControlTimestamp); found { 67 | meta["ts"] = ts 68 | } 69 | if hop, found := msg.GetUint32Header(ControlHopCount); found { 70 | meta["hopCount"] = hop 71 | } 72 | if hopType, found := msg.GetStringHeader(ControlHopType); found { 73 | meta["hopType"] = hopType 74 | } 75 | if hopId, found := msg.GetStringHeader(ControlHopId); found { 76 | meta["hopId"] = hopId 77 | } 78 | if userVal, found := msg.GetUint32Header(ControlUserVal); found { 79 | meta["uv"] = userVal 80 | } 81 | if hopErr, found := msg.GetUint32Header(ControlError); found { 82 | meta["err"] = hopErr 83 | } 84 | } 85 | data, err := meta.MarshalTraceMessageDecode() 86 | if err != nil { 87 | return nil, true 88 | } 89 | 90 | return data, true 91 | 92 | } else { 93 | pfxlog.Logger().WithError(err).Error("unexpected error unmarshalling control msg") 94 | } 95 | } 96 | 97 | return nil, false 98 | } 99 | 100 | func DecodePayload(payload *Payload) ([]byte, bool) { 101 | meta := channel.NewTraceMessageDecode(DECODER, "Payload") 102 | meta["circuitId"] = payload.CircuitId 103 | meta["sequence"] = payload.Sequence 104 | switch payload.GetOriginator() { 105 | case Initiator: 106 | meta["originator"] = "i" 107 | case Terminator: 108 | meta["originator"] = "e" 109 | } 110 | if payload.Flags != 0 { 111 | meta["flags"] = payload.Flags 112 | } 113 | meta["length"] = len(payload.Data) 114 | 115 | data, err := meta.MarshalTraceMessageDecode() 116 | if err != nil { 117 | return nil, true 118 | } 119 | 120 | return data, true 121 | } 122 | -------------------------------------------------------------------------------- /xgress/heartbeat_transformer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package xgress 18 | 19 | import ( 20 | "encoding/binary" 21 | "github.com/openziti/channel/v4" 22 | "time" 23 | ) 24 | 25 | type PayloadTransformer struct { 26 | } 27 | 28 | func (self PayloadTransformer) Rx(*channel.Message, channel.Channel) {} 29 | 30 | func (self PayloadTransformer) Tx(m *channel.Message, ch channel.Channel) { 31 | if m.ContentType == channel.ContentTypeRaw && len(m.Body) > 1 { 32 | if m.Body[0]&HeartbeatFlagMask != 0 && len(m.Body) > 12 { 33 | now := time.Now().UnixNano() 34 | m.PutUint64Header(channel.HeartbeatHeader, uint64(now)) 35 | binary.BigEndian.PutUint64(m.Body[len(m.Body)-8:], uint64(now)) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /xgress/link_receive_buffer.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package xgress 18 | 19 | import ( 20 | "fmt" 21 | "github.com/emirpasic/gods/trees/btree" 22 | "github.com/emirpasic/gods/utils" 23 | "github.com/michaelquigley/pfxlog" 24 | "sync/atomic" 25 | "time" 26 | ) 27 | 28 | type LinkReceiveBuffer struct { 29 | tree *btree.Tree 30 | sequence int32 31 | maxSequence int32 32 | size uint32 33 | lastBufferSizeSent uint32 34 | } 35 | 36 | func NewLinkReceiveBuffer() *LinkReceiveBuffer { 37 | return &LinkReceiveBuffer{ 38 | tree: btree.NewWith(10240, utils.Int32Comparator), 39 | sequence: -1, 40 | } 41 | } 42 | 43 | func (buffer *LinkReceiveBuffer) Size() uint32 { 44 | return atomic.LoadUint32(&buffer.size) 45 | } 46 | 47 | func (buffer *LinkReceiveBuffer) ReceiveUnordered(x *Xgress, payload *Payload, maxSize uint32) bool { 48 | if payload.GetSequence() <= buffer.sequence { 49 | x.dataPlane.GetMetrics().MarkDuplicatePayload() 50 | return true 51 | } 52 | 53 | if atomic.LoadUint32(&buffer.size) > maxSize && payload.Sequence > buffer.maxSequence { 54 | x.dataPlane.GetMetrics().MarkPayloadDropped() 55 | return false 56 | } 57 | 58 | treeSize := buffer.tree.Size() 59 | buffer.tree.Put(payload.GetSequence(), payload) 60 | if buffer.tree.Size() > treeSize { 61 | payloadSize := len(payload.Data) 62 | size := atomic.AddUint32(&buffer.size, uint32(payloadSize)) 63 | pfxlog.Logger().Tracef("Payload %v of size %v added to transmit buffer. New size: %v", payload.Sequence, payloadSize, size) 64 | if payload.Sequence > buffer.maxSequence { 65 | buffer.maxSequence = payload.Sequence 66 | } 67 | } else { 68 | x.dataPlane.GetMetrics().MarkDuplicatePayload() 69 | } 70 | return true 71 | } 72 | 73 | func (buffer *LinkReceiveBuffer) PeekHead() *Payload { 74 | if val := buffer.tree.LeftValue(); val != nil { 75 | payload := val.(*Payload) 76 | if payload.Sequence == buffer.sequence+1 { 77 | return payload 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | func (buffer *LinkReceiveBuffer) Remove(payload *Payload) { 84 | buffer.tree.Remove(payload.Sequence) 85 | buffer.sequence = payload.Sequence 86 | } 87 | 88 | func (buffer *LinkReceiveBuffer) getLastBufferSizeSent() uint32 { 89 | return atomic.LoadUint32(&buffer.lastBufferSizeSent) 90 | } 91 | 92 | func (buffer *LinkReceiveBuffer) Inspect(x *Xgress) *RecvBufferDetail { 93 | timeout := time.After(100 * time.Millisecond) 94 | inspectEvent := &receiveBufferInspectEvent{ 95 | buffer: buffer, 96 | notifyComplete: make(chan *RecvBufferDetail, 1), 97 | } 98 | 99 | if x.dataPlane.GetPayloadIngester().inspect(inspectEvent, timeout) { 100 | select { 101 | case result := <-inspectEvent.notifyComplete: 102 | return result 103 | case <-timeout: 104 | } 105 | } 106 | 107 | return buffer.inspectIncomplete() 108 | } 109 | 110 | func (buffer *LinkReceiveBuffer) inspectComplete() *RecvBufferDetail { 111 | nextPayload := "none" 112 | if head := buffer.tree.LeftValue(); head != nil { 113 | payload := head.(*Payload) 114 | nextPayload = fmt.Sprintf("%v", payload.Sequence) 115 | } 116 | 117 | return &RecvBufferDetail{ 118 | Size: buffer.Size(), 119 | PayloadCount: uint32(buffer.tree.Size()), 120 | LastSizeSent: buffer.getLastBufferSizeSent(), 121 | Sequence: buffer.sequence, 122 | MaxSequence: buffer.maxSequence, 123 | NextPayload: nextPayload, 124 | AcquiredSafely: true, 125 | } 126 | } 127 | 128 | func (buffer *LinkReceiveBuffer) inspectIncomplete() *RecvBufferDetail { 129 | return &RecvBufferDetail{ 130 | Size: buffer.Size(), 131 | LastSizeSent: buffer.getLastBufferSizeSent(), 132 | Sequence: buffer.sequence, 133 | MaxSequence: buffer.maxSequence, 134 | NextPayload: "unsafe to check", 135 | AcquiredSafely: false, 136 | } 137 | } 138 | 139 | type receiveBufferInspectEvent struct { 140 | buffer *LinkReceiveBuffer 141 | notifyComplete chan *RecvBufferDetail 142 | } 143 | 144 | func (self *receiveBufferInspectEvent) handle() { 145 | result := self.buffer.inspectComplete() 146 | self.notifyComplete <- result 147 | } 148 | -------------------------------------------------------------------------------- /xgress/messages_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package xgress 18 | 19 | import ( 20 | "github.com/stretchr/testify/assert" 21 | "reflect" 22 | "sync/atomic" 23 | "testing" 24 | ) 25 | 26 | // A simple test to check for failure of alignment on atomic operations for 64 bit variables in a struct 27 | func Test64BitAlignment(t *testing.T) { 28 | defer func() { 29 | if r := recover(); r != nil { 30 | t.Errorf("One of the variables that was tested is not properly 64-bit aligned.") 31 | } 32 | }() 33 | 34 | lsb := Xgress{} 35 | tPayload := txPayload{} 36 | reTx := Retransmitter{} 37 | 38 | atomic.LoadInt64(&lsb.timeOfLastRxFromLink) 39 | atomic.LoadInt64(&tPayload.age) 40 | atomic.LoadInt64(&reTx.retransmitsQueueSize) 41 | } 42 | 43 | func TestSetOriginatorFlag(t *testing.T) { 44 | type args struct { 45 | flags uint32 46 | originator Originator 47 | } 48 | tests := []struct { 49 | name string 50 | args args 51 | want uint32 52 | }{ 53 | 54 | {name: "set empty to ingress", 55 | args: args{ 56 | flags: 0, 57 | originator: Initiator, 58 | }, 59 | want: 0, 60 | }, 61 | {name: "set end of circuit to ingress", 62 | args: args{ 63 | flags: uint32(PayloadFlagCircuitEnd), 64 | originator: Initiator, 65 | }, 66 | want: uint32(PayloadFlagCircuitEnd), 67 | }, 68 | {name: "set empty to egress", 69 | args: args{ 70 | flags: 0, 71 | originator: Terminator, 72 | }, 73 | want: uint32(PayloadFlagOriginator), 74 | }, 75 | {name: "set end of circuit to egress", 76 | args: args{ 77 | flags: uint32(PayloadFlagCircuitEnd), 78 | originator: Terminator, 79 | }, 80 | want: uint32(PayloadFlagCircuitEnd) | uint32(PayloadFlagOriginator), 81 | }, 82 | } 83 | for _, tt := range tests { 84 | t.Run(tt.name, func(t *testing.T) { 85 | if got := SetOriginatorFlag(tt.args.flags, tt.args.originator); got != tt.want { 86 | t.Errorf("SetOriginatorFlag() = %v, want %v", got, tt.want) 87 | } 88 | }) 89 | } 90 | } 91 | 92 | func TestAcknowledgement_marshallSequence(t *testing.T) { 93 | tests := []struct { 94 | name string 95 | sequence []int32 96 | }{ 97 | 98 | {name: "nil", sequence: nil}, 99 | {name: "empty", sequence: make([]int32, 0)}, 100 | {name: "one entry", sequence: []int32{1}}, 101 | {name: "many entries", sequence: []int32{1, -1, 100, 200, -3213232, 421123, -58903204, -4324, 432432, 0, 9}}, 102 | } 103 | for _, tt := range tests { 104 | t.Run(tt.name, func(t *testing.T) { 105 | ack := &Acknowledgement{ 106 | Sequence: tt.sequence, 107 | } 108 | got := ack.marshallSequence() 109 | ack2 := &Acknowledgement{} 110 | err := ack2.unmarshallSequence(got) 111 | assert.NoError(t, err) 112 | 113 | if len(ack.Sequence) == 0 { 114 | return 115 | } 116 | if !reflect.DeepEqual(ack, ack2) { 117 | t.Errorf("marshallSequence() = %v, want %v", ack2, ack) 118 | } 119 | }) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /xgress/metrics.go: -------------------------------------------------------------------------------- 1 | package xgress 2 | 3 | import ( 4 | "github.com/openziti/metrics" 5 | "sync/atomic" 6 | "time" 7 | ) 8 | 9 | type Metrics interface { 10 | MarkAckReceived() 11 | MarkPayloadDropped() 12 | MarkDuplicateAck() 13 | MarkDuplicatePayload() 14 | BufferBlockedByLocalWindow() 15 | BufferUnblockedByLocalWindow() 16 | BufferBlockedByRemoteWindow() 17 | BufferUnblockedByRemoteWindow() 18 | 19 | PayloadWritten(duration time.Duration) 20 | BufferUnblocked(duration time.Duration) 21 | 22 | SendPayloadBuffered(payloadSize int64) 23 | SendPayloadDelivered(payloadSize int64) 24 | } 25 | 26 | type metricsImpl struct { 27 | ackRxMeter metrics.Meter 28 | droppedPayloadsMeter metrics.Meter 29 | 30 | payloadWriteTimer metrics.Timer 31 | duplicateAcksMeter metrics.Meter 32 | duplicatePayloadsMeter metrics.Meter 33 | 34 | buffersBlockedByLocalWindow int64 35 | buffersBlockedByRemoteWindow int64 36 | outstandingPayloads int64 37 | outstandingPayloadBytes int64 38 | 39 | buffersBlockedByLocalWindowMeter metrics.Meter 40 | buffersBlockedByRemoteWindowMeter metrics.Meter 41 | 42 | bufferBlockedTime metrics.Timer 43 | } 44 | 45 | func (self *metricsImpl) SendPayloadBuffered(payloadSize int64) { 46 | atomic.AddInt64(&self.outstandingPayloads, 1) 47 | atomic.AddInt64(&self.outstandingPayloadBytes, payloadSize) 48 | } 49 | 50 | func (self *metricsImpl) SendPayloadDelivered(payloadSize int64) { 51 | atomic.AddInt64(&self.outstandingPayloads, -1) 52 | atomic.AddInt64(&self.outstandingPayloadBytes, -payloadSize) 53 | } 54 | 55 | func (self *metricsImpl) MarkAckReceived() { 56 | self.ackRxMeter.Mark(1) 57 | } 58 | 59 | func (self *metricsImpl) MarkPayloadDropped() { 60 | self.droppedPayloadsMeter.Mark(1) 61 | } 62 | 63 | func (self *metricsImpl) MarkDuplicateAck() { 64 | self.duplicateAcksMeter.Mark(1) 65 | } 66 | 67 | func (self *metricsImpl) MarkDuplicatePayload() { 68 | self.duplicatePayloadsMeter.Mark(1) 69 | } 70 | 71 | func (self *metricsImpl) BufferBlockedByLocalWindow() { 72 | atomic.AddInt64(&self.buffersBlockedByLocalWindow, 1) 73 | self.buffersBlockedByLocalWindowMeter.Mark(1) 74 | } 75 | 76 | func (self *metricsImpl) BufferUnblockedByLocalWindow() { 77 | atomic.AddInt64(&self.buffersBlockedByLocalWindow, -1) 78 | } 79 | 80 | func (self *metricsImpl) BufferBlockedByRemoteWindow() { 81 | atomic.AddInt64(&self.buffersBlockedByRemoteWindow, 1) 82 | self.buffersBlockedByRemoteWindowMeter.Mark(1) 83 | } 84 | 85 | func (self *metricsImpl) BufferUnblockedByRemoteWindow() { 86 | atomic.AddInt64(&self.buffersBlockedByRemoteWindow, -1) 87 | } 88 | 89 | func (self *metricsImpl) PayloadWritten(duration time.Duration) { 90 | self.payloadWriteTimer.Update(duration) 91 | } 92 | 93 | func (self *metricsImpl) BufferUnblocked(duration time.Duration) { 94 | self.bufferBlockedTime.Update(duration) 95 | } 96 | 97 | func NewMetrics(registry metrics.Registry) Metrics { 98 | impl := &metricsImpl{ 99 | droppedPayloadsMeter: registry.Meter("xgress.dropped_payloads"), 100 | ackRxMeter: registry.Meter("xgress.rx.acks"), 101 | payloadWriteTimer: registry.Timer("xgress.tx_write_time"), 102 | duplicateAcksMeter: registry.Meter("xgress.ack_duplicates"), 103 | duplicatePayloadsMeter: registry.Meter("xgress.payload_duplicates"), 104 | buffersBlockedByLocalWindowMeter: registry.Meter("xgress.blocked_by_local_window_rate"), 105 | buffersBlockedByRemoteWindowMeter: registry.Meter("xgress.blocked_by_remote_window_rate"), 106 | bufferBlockedTime: registry.Timer("xgress.blocked_time"), 107 | } 108 | 109 | registry.FuncGauge("xgress.blocked_by_local_window", func() int64 { 110 | return atomic.LoadInt64(&impl.buffersBlockedByLocalWindow) 111 | }) 112 | 113 | registry.FuncGauge("xgress.blocked_by_remote_window", func() int64 { 114 | return atomic.LoadInt64(&impl.buffersBlockedByRemoteWindow) 115 | }) 116 | 117 | registry.FuncGauge("xgress.tx_unacked_payloads", func() int64 { 118 | return atomic.LoadInt64(&impl.outstandingPayloads) 119 | }) 120 | 121 | registry.FuncGauge("xgress.tx_unacked_payload_bytes", func() int64 { 122 | return atomic.LoadInt64(&impl.outstandingPayloadBytes) 123 | }) 124 | 125 | return impl 126 | } 127 | -------------------------------------------------------------------------------- /xgress/ordering_test.go: -------------------------------------------------------------------------------- 1 | package xgress 2 | 3 | import ( 4 | "encoding/binary" 5 | "github.com/openziti/channel/v4" 6 | "github.com/stretchr/testify/require" 7 | "io" 8 | "sync/atomic" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | type testConn struct { 14 | ch chan uint64 15 | closeNotify chan struct{} 16 | closed atomic.Bool 17 | } 18 | 19 | func (conn *testConn) Close() error { 20 | if conn.closed.CompareAndSwap(false, true) { 21 | close(conn.closeNotify) 22 | } 23 | return nil 24 | } 25 | 26 | func (conn *testConn) LogContext() string { 27 | return "test" 28 | } 29 | 30 | func (conn *testConn) ReadPayload() ([]byte, map[uint8][]byte, error) { 31 | <-conn.closeNotify 32 | return nil, nil, io.EOF 33 | } 34 | 35 | func (conn *testConn) WritePayload(bytes []byte, _ map[uint8][]byte) (int, error) { 36 | val := binary.LittleEndian.Uint64(bytes) 37 | conn.ch <- val 38 | return len(bytes), nil 39 | } 40 | 41 | func (conn *testConn) HandleControlMsg(ControlType, channel.Headers, ControlReceiver) error { 42 | return nil 43 | } 44 | 45 | type noopReceiveHandler struct { 46 | payloadIngester *PayloadIngester 47 | } 48 | 49 | func (n noopReceiveHandler) RetransmitPayload(srcAddr Address, payload *Payload) error { 50 | return nil 51 | } 52 | 53 | func (n noopReceiveHandler) GetMetrics() Metrics { 54 | return noopMetrics{} 55 | } 56 | 57 | func (n noopReceiveHandler) GetRetransmitter() *Retransmitter { 58 | return nil 59 | } 60 | 61 | func (n noopReceiveHandler) GetPayloadIngester() *PayloadIngester { 62 | return n.payloadIngester 63 | } 64 | 65 | func (n noopReceiveHandler) ForwardAcknowledgement(*Acknowledgement, Address) {} 66 | 67 | func (n noopReceiveHandler) ForwardPayload(*Payload, *Xgress) {} 68 | 69 | func (n noopReceiveHandler) ForwardControlMessage(*Control, *Xgress) {} 70 | 71 | func Test_Ordering(t *testing.T) { 72 | closeNotify := make(chan struct{}) 73 | 74 | conn := &testConn{ 75 | ch: make(chan uint64, 1), 76 | closeNotify: make(chan struct{}), 77 | } 78 | 79 | x := NewXgress("test", "ctrl", "test", conn, Initiator, DefaultOptions(), nil) 80 | x.dataPlane = noopReceiveHandler{ 81 | payloadIngester: NewPayloadIngester(closeNotify), 82 | } 83 | go x.tx() 84 | 85 | defer x.Close() 86 | 87 | msgCount := 100000 88 | 89 | errorCh := make(chan error, 1) 90 | 91 | go func() { 92 | for i := 0; i < msgCount; i++ { 93 | data := make([]byte, 8) 94 | binary.LittleEndian.PutUint64(data, uint64(i)) 95 | payload := &Payload{ 96 | CircuitId: "test", 97 | Flags: SetOriginatorFlag(0, Terminator), 98 | RTT: 0, 99 | Sequence: int32(i), 100 | Headers: nil, 101 | Data: data, 102 | } 103 | if err := x.SendPayload(payload, 0, PayloadTypeXg); err != nil { 104 | errorCh <- err 105 | x.Close() 106 | return 107 | } 108 | } 109 | }() 110 | 111 | timeout := time.After(20 * time.Second) 112 | 113 | req := require.New(t) 114 | for i := 0; i < msgCount; i++ { 115 | select { 116 | case next := <-conn.ch: 117 | req.Equal(uint64(i), next) 118 | case <-conn.closeNotify: 119 | req.Fail("test failed with count at %v", i) 120 | case err := <-errorCh: 121 | req.NoError(err) 122 | case <-timeout: 123 | req.Failf("timed out", "count at %v", i) 124 | } 125 | } 126 | } 127 | 128 | type noopMetrics struct{} 129 | 130 | func (n noopMetrics) MarkAckReceived() {} 131 | 132 | func (n noopMetrics) MarkPayloadDropped() {} 133 | 134 | func (n noopMetrics) MarkDuplicateAck() {} 135 | 136 | func (n noopMetrics) MarkDuplicatePayload() {} 137 | 138 | func (n noopMetrics) BufferBlockedByLocalWindow() {} 139 | 140 | func (n noopMetrics) BufferUnblockedByLocalWindow() {} 141 | 142 | func (n noopMetrics) BufferBlockedByRemoteWindow() {} 143 | 144 | func (n noopMetrics) BufferUnblockedByRemoteWindow() {} 145 | 146 | func (n noopMetrics) PayloadWritten(time.Duration) {} 147 | 148 | func (n noopMetrics) BufferUnblocked(time.Duration) {} 149 | 150 | func (n noopMetrics) SendPayloadBuffered(int64) {} 151 | 152 | func (n noopMetrics) SendPayloadDelivered(int64) {} 153 | -------------------------------------------------------------------------------- /xgress/payload_ingester.go: -------------------------------------------------------------------------------- 1 | package xgress 2 | 3 | import "time" 4 | 5 | type payloadEntry struct { 6 | payload *Payload 7 | x *Xgress 8 | } 9 | 10 | type PayloadIngester struct { 11 | payloadIngest chan *payloadEntry 12 | payloadSendReq chan *Xgress 13 | receiveBufferInspects chan *receiveBufferInspectEvent 14 | closeNotify <-chan struct{} 15 | } 16 | 17 | func NewPayloadIngester(closeNotify <-chan struct{}) *PayloadIngester { 18 | pi := &PayloadIngester{ 19 | payloadIngest: make(chan *payloadEntry, 16), 20 | payloadSendReq: make(chan *Xgress, 16), 21 | receiveBufferInspects: make(chan *receiveBufferInspectEvent, 4), 22 | closeNotify: closeNotify, 23 | } 24 | 25 | go pi.run() 26 | 27 | return pi 28 | } 29 | 30 | func (self *PayloadIngester) inspect(evt *receiveBufferInspectEvent, timeout <-chan time.Time) bool { 31 | select { 32 | case self.receiveBufferInspects <- evt: 33 | return true 34 | case <-self.closeNotify: 35 | case <-timeout: 36 | } 37 | return false 38 | } 39 | 40 | func (self *PayloadIngester) ingest(payload *Payload, x *Xgress) { 41 | self.payloadIngest <- &payloadEntry{ 42 | payload: payload, 43 | x: x, 44 | } 45 | } 46 | 47 | func (self *PayloadIngester) run() { 48 | for { 49 | select { 50 | case payloadEntry := <-self.payloadIngest: 51 | payloadEntry.x.acceptPayload(payloadEntry.payload) 52 | case x := <-self.payloadSendReq: 53 | x.queueSends() 54 | case evt := <-self.receiveBufferInspects: 55 | evt.handle() 56 | case <-self.closeNotify: 57 | return 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /xgress/retransmitter.go: -------------------------------------------------------------------------------- 1 | package xgress 2 | 3 | import ( 4 | "github.com/michaelquigley/pfxlog" 5 | "github.com/openziti/metrics" 6 | "sync/atomic" 7 | ) 8 | 9 | type RetransmitterFaultReporter interface { 10 | ReportForwardingFault(circuitId string, ctrlId string) 11 | } 12 | 13 | type Retransmitter struct { 14 | faultReporter RetransmitterFaultReporter 15 | retxTail *txPayload 16 | retxHead *txPayload 17 | retransmitIngest chan *txPayload 18 | retransmitSend chan *txPayload 19 | retransmitsQueueSize int64 20 | closeNotify <-chan struct{} 21 | 22 | retransmissions metrics.Meter 23 | retransmissionFailures metrics.Meter 24 | } 25 | 26 | func NewRetransmitter(faultReporter RetransmitterFaultReporter, metrics metrics.Registry, closeNotify <-chan struct{}) *Retransmitter { 27 | ctrl := &Retransmitter{ 28 | retransmitIngest: make(chan *txPayload, 16), 29 | retransmitSend: make(chan *txPayload, 1), 30 | closeNotify: closeNotify, 31 | faultReporter: faultReporter, 32 | 33 | retransmissions: metrics.Meter("xgress.retransmissions"), 34 | retransmissionFailures: metrics.Meter("xgress.retransmission_failures"), 35 | } 36 | 37 | go ctrl.retransmitIngester() 38 | go ctrl.retransmitSender() 39 | 40 | metrics.FuncGauge("xgress.retransmits.queue_size", func() int64 { 41 | return atomic.LoadInt64(&ctrl.retransmitsQueueSize) 42 | }) 43 | 44 | return ctrl 45 | } 46 | 47 | func (self *Retransmitter) queue(p *txPayload) { 48 | self.retransmitIngest <- p 49 | } 50 | 51 | func (self *Retransmitter) popHead() *txPayload { 52 | if self.retxHead == nil { 53 | return nil 54 | } 55 | 56 | result := self.retxHead 57 | if result.prev == nil { 58 | self.retxHead = nil 59 | self.retxTail = nil 60 | } else { 61 | self.retxHead = result.prev 62 | result.prev.next = nil 63 | } 64 | 65 | result.prev = nil 66 | result.next = nil 67 | 68 | atomic.AddInt64(&self.retransmitsQueueSize, -1) 69 | 70 | return result 71 | } 72 | 73 | func (self *Retransmitter) pushTail(txp *txPayload) { 74 | if txp.prev != nil || txp.next != nil || txp == self.retxHead { 75 | return 76 | } 77 | if self.retxHead == nil { 78 | self.retxTail = txp 79 | self.retxHead = txp 80 | } else { 81 | txp.next = self.retxTail 82 | self.retxTail.prev = txp 83 | self.retxTail = txp 84 | } 85 | atomic.AddInt64(&self.retransmitsQueueSize, 1) 86 | } 87 | 88 | func (self *Retransmitter) delete(txp *txPayload) { 89 | if self.retxHead == txp { 90 | self.popHead() 91 | } else if txp == self.retxTail { 92 | self.retxTail = txp.next 93 | self.retxTail.prev = nil 94 | atomic.AddInt64(&self.retransmitsQueueSize, -1) 95 | } else if txp.prev != nil { 96 | txp.prev.next = txp.next 97 | txp.next.prev = txp.prev 98 | atomic.AddInt64(&self.retransmitsQueueSize, -1) 99 | } 100 | 101 | txp.prev = nil 102 | txp.next = nil 103 | } 104 | 105 | func (self *Retransmitter) retransmitIngester() { 106 | var next *txPayload 107 | for { 108 | if next == nil { 109 | next = self.popHead() 110 | } 111 | 112 | if next == nil { 113 | select { 114 | case retransmit := <-self.retransmitIngest: 115 | self.acceptRetransmit(retransmit) 116 | case <-self.closeNotify: 117 | return 118 | } 119 | } else { 120 | select { 121 | case retransmit := <-self.retransmitIngest: 122 | self.acceptRetransmit(retransmit) 123 | case self.retransmitSend <- next: 124 | next = nil 125 | case <-self.closeNotify: 126 | return 127 | } 128 | } 129 | } 130 | } 131 | 132 | func (self *Retransmitter) acceptRetransmit(txp *txPayload) { 133 | if txp.isAcked() { 134 | self.delete(txp) 135 | } else { 136 | self.pushTail(txp) 137 | } 138 | } 139 | 140 | func (self *Retransmitter) retransmitSender() { 141 | logger := pfxlog.Logger() 142 | for { 143 | select { 144 | case retransmit := <-self.retransmitSend: 145 | if !retransmit.isAcked() { 146 | retransmit.payload.MarkAsRetransmit() 147 | if err := retransmit.x.dataPlane.RetransmitPayload(retransmit.x.address, retransmit.payload); err != nil { 148 | // if xgress is closed, don't log the error. We still want to try retransmitting in case we're re-sending end of circuit 149 | if !retransmit.x.Closed() { 150 | logger.WithError(err).Errorf("unexpected error while retransmitting payload from [@/%v]", retransmit.x.address) 151 | self.retransmissionFailures.Mark(1) 152 | self.faultReporter.ReportForwardingFault(retransmit.payload.CircuitId, retransmit.x.ctrlId) 153 | } else { 154 | logger.WithError(err).Tracef("unexpected error while retransmitting payload from [@/%v] (already closed)", retransmit.x.address) 155 | } 156 | } else { 157 | retransmit.markSent() 158 | self.retransmissions.Mark(1) 159 | } 160 | retransmit.dequeued() 161 | } 162 | case <-self.closeNotify: 163 | return 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /ziti/default_collection.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ziti 18 | 19 | // Deprecated: DefaultCollection is deprecated and is included for legacy support. 20 | // It powers two other deprecated functions: `ForAllContext() and and `LoadContext()` which rely on it. The intended 21 | // replacement is for implementations that wish to have this functionality to use NewSdkCollection() or 22 | // NewSdkCollectionFromEnv() on their own. 23 | var DefaultCollection *CtxCollection 24 | 25 | // IdentitiesEnv is the string environment variable that is used to load identity files to populate DefaultCollection 26 | const IdentitiesEnv = "ZITI_IDENTITIES" 27 | 28 | func init() { 29 | DefaultCollection = NewSdkCollectionFromEnv(IdentitiesEnv, InterceptV1, ClientConfigV1) 30 | } 31 | 32 | // Deprecated: ForAllContexts iterates over all Context instances in the DefaultCollection and call the provided function `f`. 33 | // Usage of the DefaultCollection is advised against, and if this functionality is needed, implementations should 34 | // instantiate their own CtxCollection via NewSdkCollection() or NewSdkCollectionFromEnv() 35 | func ForAllContexts(f func(ctx Context) bool) { 36 | //Recreates sync.Map's Range() function which uses a bool return value to stop iterating (false == stop). 37 | //Done for backwards compatibility with ForAllContexts implementation 38 | keepGoing := true 39 | DefaultCollection.ForAll(func(c Context) { 40 | if !keepGoing { 41 | return 42 | } 43 | keepGoing = f(c) 44 | }) 45 | } 46 | 47 | // Deprecated: LoadContext loads a configuration from the supplied path into the DefaultCollection as a convenience. 48 | // Usage of the DefaultCollection is advised against, and if this functionality is needed, implementations should 49 | // instantiate their own CtxCollection via NewSdkCollection() or NewSdkCollectionFromEnv(). 50 | // 51 | // This function's behavior can be replicated with: 52 | // ``` 53 | // 54 | // collection = NewSdkCollection() 55 | // collection.ConfigTypes = []string{InterceptV1, ClientConfigV1} 56 | // collection.NewContextFromFile(configPath) 57 | // 58 | // ``` 59 | // 60 | // LoadContext will attempt to load a Config from the provided path, see NewConfigFromFile() for details. Additionally, 61 | // LoadContext will attempt to authenticate the Context. If it does not authenticate, it will not be added to the 62 | // DefaultCollection and an error will be returned. 63 | // ``` 64 | func LoadContext(configPath string) (Context, error) { 65 | ctx, err := DefaultCollection.NewContextFromFile(configPath) 66 | 67 | if err != nil { 68 | return nil, err 69 | } 70 | 71 | err = ctx.Authenticate() 72 | 73 | if err != nil { 74 | DefaultCollection.Remove(ctx) 75 | ctx.Close() 76 | } 77 | 78 | return ctx, nil 79 | } 80 | -------------------------------------------------------------------------------- /ziti/dialer.go: -------------------------------------------------------------------------------- 1 | package ziti 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "github.com/openziti/edge-api/rest_model" 7 | "math" 8 | "net" 9 | "strconv" 10 | ) 11 | 12 | type Dialer interface { 13 | Dial(network, address string) (net.Conn, error) 14 | } 15 | 16 | type ContextDialer interface { 17 | DialContext(ctx context.Context, network, address string) (net.Conn, error) 18 | } 19 | 20 | type dialer struct { 21 | fallback Dialer 22 | context context.Context 23 | collection *CtxCollection 24 | } 25 | 26 | // Deprecated: NewDialer will return a dialer from the DefaultCollection that will iterate over the Context instances 27 | // inside the collection searching for the context that best matches the service. 28 | // 29 | // It is suggested that implementations construct their own CtxCollection and use the NewDialer/NewDialerWithFallback present there. 30 | // 31 | // If a matching service is not found, an error is returned. Matching is based on Match() logic in edge.InterceptV1Config. 32 | func NewDialer() Dialer { 33 | return DefaultCollection.NewDialer() 34 | } 35 | 36 | // Deprecated: NewDialerWithFallback will return a dialer from the DefaultCollection that will iterate over the Context 37 | // instances inside the collection searching for the context that best matches the service. 38 | // 39 | // It is suggested that implementations construct their own CtxCollection and use the NewDialer/NewDialerWithFallback present there. 40 | // 41 | // If a matching service is not found, a dial is attempted with the fallback dialer. Matching is based on Match() logic 42 | // in edge.InterceptV1Config. 43 | func NewDialerWithFallback(ctx context.Context, fallback Dialer) Dialer { 44 | return DefaultCollection.NewDialerWithFallback(ctx, fallback) 45 | } 46 | 47 | func (dialer *dialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) { 48 | dialer.context = ctx 49 | return dialer.Dial(network, address) 50 | } 51 | 52 | func (dialer *dialer) Dial(network, address string) (net.Conn, error) { 53 | host, portString, err := net.SplitHostPort(address) 54 | 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | port, err := strconv.Atoi(portString) 60 | if err != nil { 61 | return nil, err 62 | } 63 | 64 | network = normalizeProtocol(network) 65 | 66 | var ztx Context 67 | var service *rest_model.ServiceDetail 68 | var bestFound = false 69 | best := math.MaxInt 70 | dialer.collection.ForAll(func(ctx Context) { 71 | if bestFound { 72 | return 73 | } 74 | 75 | srv, score, err := ctx.GetServiceForAddr(network, host, uint16(port)) 76 | if err == nil { 77 | if score < best { 78 | best = score 79 | ztx = ctx 80 | service = srv 81 | } 82 | 83 | if score == 0 { // best possible score 84 | bestFound = true 85 | } 86 | } 87 | }) 88 | 89 | if ztx != nil && service != nil { 90 | return ztx.(*ContextImpl).dialServiceFromAddr(*service.Name, network, host, uint16(port)) 91 | } 92 | 93 | if dialer.fallback != nil { 94 | ctxDialer, ok := dialer.fallback.(ContextDialer) 95 | if ok && dialer.context != nil { 96 | return ctxDialer.DialContext(dialer.context, network, address) 97 | } else { 98 | return dialer.fallback.Dial(network, address) 99 | } 100 | } 101 | 102 | return nil, fmt.Errorf("address [%s:%s:%d] is not intercepted by any ziti context", network, host, port) 103 | } 104 | 105 | func normalizeProtocol(proto string) string { 106 | switch proto { 107 | case "tcp", "tcp4", "tcp6": 108 | return "tcp" 109 | case "udp", "udp4", "udp6": 110 | return "udp" 111 | default: 112 | return proto 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /ziti/edge/addr.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package edge 18 | 19 | import ( 20 | "fmt" 21 | ) 22 | 23 | type Addr struct { 24 | MsgCh MsgChannel 25 | } 26 | 27 | func (e *Addr) Network() string { 28 | return "ziti-edge" 29 | } 30 | 31 | func (e *Addr) String() string { 32 | return fmt.Sprintf("ziti-edge-router connId=%v, logical=%v", e.MsgCh.Id(), e.MsgCh.GetChannel().LogicalName()) 33 | } 34 | -------------------------------------------------------------------------------- /ziti/edge/addr_parsers.go: -------------------------------------------------------------------------------- 1 | //go:build !js 2 | 3 | /* 4 | Copyright NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package edge 20 | 21 | import ( 22 | "github.com/openziti/transport/v2" 23 | "github.com/openziti/transport/v2/tls" 24 | ) 25 | 26 | func AddAddressParsers() { 27 | transport.AddAddressParser(tls.AddressParser{}) 28 | } 29 | -------------------------------------------------------------------------------- /ziti/edge/addr_parsers_js.go: -------------------------------------------------------------------------------- 1 | //go:build js 2 | 3 | /* 4 | Copyright NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package edge 20 | 21 | import ( 22 | "github.com/openziti/transport/v2" 23 | "github.com/openziti/transport/v2/wss" 24 | ) 25 | 26 | func AddAddressParsers() { 27 | transport.AddAddressParser(wss.AddressParser{}) 28 | } 29 | -------------------------------------------------------------------------------- /ziti/edge/network/msg_timer.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "github.com/openziti/channel/v4" 6 | "github.com/openziti/metrics" 7 | "sort" 8 | "strings" 9 | "sync/atomic" 10 | "time" 11 | ) 12 | 13 | type timingReceiveHandler struct { 14 | handler channel.ReceiveHandler 15 | timer metrics.Histogram 16 | } 17 | 18 | func (t *timingReceiveHandler) HandleReceive(m *channel.Message, ch channel.Channel) { 19 | start := time.Now() 20 | t.handler.HandleReceive(m, ch) 21 | t.timer.Update(int64(time.Since(start))) 22 | } 23 | 24 | func NewMessageTimingBinding(binding channel.Binding) channel.Binding { 25 | registry := metrics.NewRegistry("", nil) 26 | wrapper := &messageTimingBinding{ 27 | binding: binding, 28 | registry: registry, 29 | } 30 | closeNotify := make(chan struct{}) 31 | closed := atomic.Bool{} 32 | binding.AddCloseHandler(channel.CloseHandlerF(func(ch channel.Channel) { 33 | if closed.CompareAndSwap(false, true) { 34 | close(closeNotify) 35 | } 36 | })) 37 | reporter := metrics.NewDelegatingReporter(registry, wrapper, closeNotify) 38 | go reporter.Start(5 * time.Second) 39 | return wrapper 40 | } 41 | 42 | type messageTimingBinding struct { 43 | registry metrics.Registry 44 | binding channel.Binding 45 | output []string 46 | } 47 | 48 | func (self *messageTimingBinding) StartReport(metrics.Registry) {} 49 | 50 | func (self *messageTimingBinding) EndReport(metrics.Registry) { 51 | sort.Strings(self.output) 52 | for _, output := range self.output { 53 | fmt.Println(output) 54 | } 55 | self.output = nil 56 | } 57 | 58 | func (self *messageTimingBinding) Printf(msg string, args ...interface{}) { 59 | self.output = append(self.output, fmt.Sprintf(msg, args...)) 60 | } 61 | 62 | func (self *messageTimingBinding) Filter(name string) bool { 63 | return strings.HasSuffix(name, metrics.MetricNameCount) || 64 | strings.HasSuffix(name, metrics.MetricNamePercentile) 65 | } 66 | 67 | func (self *messageTimingBinding) AcceptIntMetric(name string, value int64) { 68 | self.Printf("%s -> %d", name, value) 69 | } 70 | 71 | func (self *messageTimingBinding) AcceptFloatMetric(name string, value float64) { 72 | self.Printf("%s -> %f", name, value) 73 | } 74 | 75 | func (self *messageTimingBinding) AcceptPercentileMetric(name string, value metrics.PercentileSource) { 76 | self.Printf("%s.50p -> %s", name, time.Duration(value.Percentile(.5)).String()) 77 | self.Printf("%s.75p -> %s", name, time.Duration(value.Percentile(.75)).String()) 78 | self.Printf("%s.95p -> %s", name, time.Duration(value.Percentile(.95)).String()) 79 | } 80 | 81 | func (self *messageTimingBinding) Bind(h channel.BindHandler) error { 82 | return h.BindChannel(self) 83 | } 84 | 85 | func (self *messageTimingBinding) AddPeekHandler(h channel.PeekHandler) { 86 | self.binding.AddPeekHandler(h) 87 | } 88 | 89 | func (self *messageTimingBinding) AddTransformHandler(h channel.TransformHandler) { 90 | self.binding.AddTransformHandler(h) 91 | } 92 | 93 | func (self *messageTimingBinding) AddReceiveHandler(contentType int32, h channel.ReceiveHandler) { 94 | timer := self.registry.Histogram(fmt.Sprintf("msgs.%d.time", contentType)) 95 | self.binding.AddReceiveHandler(contentType, &timingReceiveHandler{ 96 | handler: h, 97 | timer: timer, 98 | }) 99 | } 100 | 101 | func (self *messageTimingBinding) AddReceiveHandlerF(contentType int32, h channel.ReceiveHandlerF) { 102 | self.AddReceiveHandler(contentType, h) 103 | } 104 | 105 | func (self *messageTimingBinding) AddTypedReceiveHandler(h channel.TypedReceiveHandler) { 106 | self.AddReceiveHandler(h.ContentType(), h) 107 | } 108 | 109 | func (self *messageTimingBinding) AddErrorHandler(h channel.ErrorHandler) { 110 | self.binding.AddErrorHandler(h) 111 | } 112 | 113 | func (self *messageTimingBinding) AddCloseHandler(h channel.CloseHandler) { 114 | self.binding.AddCloseHandler(h) 115 | } 116 | 117 | func (self *messageTimingBinding) SetUserData(data interface{}) { 118 | self.binding.SetUserData(data) 119 | } 120 | 121 | func (self *messageTimingBinding) GetUserData() interface{} { 122 | return self.binding.GetUserData() 123 | } 124 | 125 | func (self *messageTimingBinding) GetChannel() channel.Channel { 126 | return self.binding.GetChannel() 127 | } 128 | -------------------------------------------------------------------------------- /ziti/edge/network/seq.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/openziti/foundation/v2/concurrenz" 5 | "github.com/pkg/errors" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | var ErrClosed = errors.New("sequencer closed") 11 | 12 | type ReadTimout struct{} 13 | 14 | func (r ReadTimout) Error() string { 15 | return "read timed out" 16 | } 17 | 18 | func (r ReadTimout) Timeout() bool { 19 | return true 20 | } 21 | 22 | func (r ReadTimout) Temporary() bool { 23 | return true 24 | } 25 | 26 | func NewNoopSequencer[T any](closeNotify <-chan struct{}, channelDepth int) *noopSeq[T] { 27 | return &noopSeq[T]{ 28 | closeNotify: closeNotify, 29 | ch: make(chan T, channelDepth), 30 | deadlineNotify: make(chan struct{}), 31 | } 32 | } 33 | 34 | type noopSeq[T any] struct { 35 | ch chan T 36 | closeNotify <-chan struct{} 37 | deadlineNotify chan struct{} 38 | deadline concurrenz.AtomicValue[time.Time] 39 | readInProgress atomic.Bool 40 | } 41 | 42 | func (seq *noopSeq[T]) PutSequenced(event T) error { 43 | select { 44 | case seq.ch <- event: 45 | return nil 46 | case <-seq.closeNotify: 47 | return ErrClosed 48 | } 49 | } 50 | 51 | func (seq *noopSeq[T]) SetReadDeadline(deadline time.Time) { 52 | seq.deadline.Store(deadline) 53 | if seq.readInProgress.Load() { 54 | select { 55 | case seq.deadlineNotify <- struct{}{}: 56 | case <-time.After(5 * time.Millisecond): 57 | } 58 | } else { 59 | select { 60 | case seq.deadlineNotify <- struct{}{}: 61 | default: 62 | } 63 | } 64 | } 65 | 66 | func (seq *noopSeq[T]) GetNext() (T, error) { 67 | seq.readInProgress.Store(true) 68 | defer seq.readInProgress.Store(false) 69 | 70 | var val T 71 | 72 | for { 73 | deadline := seq.deadline.Load() 74 | 75 | var timeoutCh <-chan time.Time 76 | 77 | if !deadline.IsZero() { 78 | timeoutCh = time.After(time.Until(deadline)) 79 | } 80 | 81 | select { 82 | case val = <-seq.ch: 83 | return val, nil 84 | case <-seq.closeNotify: 85 | // If we're closed, return any buffered values, otherwise return nil 86 | select { 87 | case val = <-seq.ch: 88 | return val, nil 89 | default: 90 | return val, ErrClosed 91 | } 92 | case <-seq.deadlineNotify: 93 | continue 94 | case <-timeoutCh: 95 | // If we're timing out, return any buffered values, otherwise return nil 96 | select { 97 | case val = <-seq.ch: 98 | return val, nil 99 | default: 100 | return val, &ReadTimout{} 101 | } 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ziti/edge/network/seq_test.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "github.com/openziti/channel/v4" 5 | "github.com/openziti/sdk-golang/ziti/edge" 6 | "github.com/stretchr/testify/require" 7 | "testing" 8 | "time" 9 | ) 10 | 11 | func Test_SeqNormalReadDeadline(t *testing.T) { 12 | closeNotify := make(chan struct{}) 13 | defer close(closeNotify) 14 | 15 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 16 | start := time.Now() 17 | readQ.SetReadDeadline(start.Add(10 * time.Millisecond)) 18 | 19 | req := require.New(t) 20 | _, err := readQ.GetNext() 21 | req.NotNil(err) 22 | req.ErrorIs(err, &ReadTimout{}) 23 | 24 | first := time.Now() 25 | req.True(first.Sub(start) >= 10*time.Millisecond) 26 | 27 | _, err = readQ.GetNext() 28 | req.NotNil(err) 29 | req.ErrorIs(err, &ReadTimout{}) 30 | req.True(time.Since(first) < time.Millisecond) 31 | } 32 | 33 | func Test_SeqNormalReadWithDeadline(t *testing.T) { 34 | closeNotify := make(chan struct{}) 35 | defer close(closeNotify) 36 | 37 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 38 | start := time.Now() 39 | readQ.SetReadDeadline(start.Add(10 * time.Millisecond)) 40 | 41 | req := require.New(t) 42 | 43 | data := make([]byte, 877) 44 | msg := edge.NewDataMsg(1, data) 45 | req.NoError(readQ.PutSequenced(msg)) 46 | 47 | val, err := readQ.GetNext() 48 | req.NoError(err) 49 | req.Equal(msg, val) 50 | } 51 | 52 | func Test_SeqNormalReadWithNoDeadline(t *testing.T) { 53 | closeNotify := make(chan struct{}) 54 | defer close(closeNotify) 55 | 56 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 57 | req := require.New(t) 58 | 59 | data := make([]byte, 877) 60 | msg := edge.NewDataMsg(1, data) 61 | req.NoError(readQ.PutSequenced(msg)) 62 | 63 | val, err := readQ.GetNext() 64 | req.NoError(err) 65 | req.Equal(msg, val) 66 | } 67 | 68 | func Test_SeqReadWithInterrupt(t *testing.T) { 69 | closeNotify := make(chan struct{}) 70 | defer close(closeNotify) 71 | 72 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 73 | start := time.Now() 74 | 75 | req := require.New(t) 76 | 77 | go func() { 78 | readQ.SetReadDeadline(start.Add(10 * time.Millisecond)) 79 | }() 80 | 81 | _, err := readQ.GetNext() 82 | req.NotNil(err) 83 | req.ErrorIs(err, &ReadTimout{}) 84 | first := time.Now() 85 | req.True(first.Sub(start) >= 10*time.Millisecond) 86 | 87 | _, err = readQ.GetNext() 88 | req.NotNil(err) 89 | req.ErrorIs(err, &ReadTimout{}) 90 | req.True(time.Since(first) < time.Millisecond) 91 | } 92 | -------------------------------------------------------------------------------- /ziti/edge/network/xg_adapter.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "github.com/michaelquigley/pfxlog" 6 | "github.com/openziti/channel/v4" 7 | "github.com/openziti/sdk-golang/edgexg" 8 | "github.com/openziti/sdk-golang/xgress" 9 | "github.com/openziti/sdk-golang/ziti/edge" 10 | "github.com/sirupsen/logrus" 11 | "io" 12 | "time" 13 | ) 14 | 15 | type XgAdapter struct { 16 | conn *edgeConn 17 | readC chan []byte 18 | closeNotify <-chan struct{} 19 | env xgress.Env 20 | xg *xgress.Xgress 21 | } 22 | 23 | func (self *XgAdapter) HandleXgressClose(x *xgress.Xgress) { 24 | self.xg.ForwardEndOfCircuit(func(payload *xgress.Payload) bool { 25 | self.ForwardPayload(payload, x) 26 | return true 27 | }) 28 | self.conn.close(true) 29 | 30 | // see note in close 31 | self.conn.msgMux.RemoveMsgSink(self.conn) 32 | 33 | xgCloseMsg := channel.NewMessage(edge.ContentTypeXgClose, []byte(self.xg.CircuitId())) 34 | if err := xgCloseMsg.WithTimeout(5 * time.Second).Send(self.conn.SdkChannel.GetControlSender()); err != nil { 35 | pfxlog.Logger().WithError(err).Error("failed to send close xg close message") 36 | } 37 | } 38 | 39 | func (self *XgAdapter) ForwardPayload(payload *xgress.Payload, x *xgress.Xgress) { 40 | msg := payload.Marshall() 41 | msg.PutUint32Header(edge.ConnIdHeader, self.conn.Id()) 42 | if err := self.conn.MsgChannel.GetDefaultSender().Send(msg); err != nil { 43 | pfxlog.Logger().WithError(err).Error("failed to send payload") 44 | } 45 | } 46 | 47 | func (self *XgAdapter) RetransmitPayload(srcAddr xgress.Address, payload *xgress.Payload) error { 48 | msg := payload.Marshall() 49 | return self.conn.MsgChannel.GetDefaultSender().Send(msg) 50 | } 51 | 52 | func (self *XgAdapter) ForwardControlMessage(control *xgress.Control, x *xgress.Xgress) { 53 | msg := control.Marshall() 54 | if err := self.conn.MsgChannel.GetDefaultSender().Send(msg); err != nil { 55 | pfxlog.Logger().WithError(err).Error("failed to forward control message") 56 | } 57 | } 58 | 59 | func (self *XgAdapter) ForwardAcknowledgement(ack *xgress.Acknowledgement, address xgress.Address) { 60 | msg := ack.Marshall() 61 | if err := self.conn.MsgChannel.GetDefaultSender().Send(msg); err != nil { 62 | pfxlog.Logger().WithError(err).Error("failed to send acknowledgement") 63 | } 64 | } 65 | 66 | func (self *XgAdapter) GetRetransmitter() *xgress.Retransmitter { 67 | return self.env.GetRetransmitter() 68 | } 69 | 70 | func (self *XgAdapter) GetPayloadIngester() *xgress.PayloadIngester { 71 | return self.env.GetPayloadIngester() 72 | } 73 | 74 | func (self *XgAdapter) GetMetrics() xgress.Metrics { 75 | return self.env.GetMetrics() 76 | } 77 | 78 | func (self *XgAdapter) Close() error { 79 | return self.conn.Close() 80 | } 81 | 82 | func (self *XgAdapter) LogContext() string { 83 | return fmt.Sprintf("xg/%s", self.conn.GetCircuitId()) 84 | } 85 | 86 | func (self *XgAdapter) Write(bytes []byte) (int, error) { 87 | select { 88 | case self.readC <- bytes: 89 | return len(bytes), nil 90 | case <-self.closeNotify: 91 | return 0, io.EOF 92 | } 93 | } 94 | 95 | func (self *XgAdapter) ReadPayload() ([]byte, map[uint8][]byte, error) { 96 | // log := pfxlog.ContextLogger(self.LogContext()).WithField("connId", self.conn.Id()) 97 | 98 | var data []byte 99 | select { 100 | case data = <-self.readC: 101 | case <-self.closeNotify: 102 | return nil, nil, io.EOF 103 | } 104 | 105 | return data, nil, nil 106 | } 107 | 108 | func (self *XgAdapter) WritePayload(bytes []byte, headers map[uint8][]byte) (int, error) { 109 | var msgUUID []byte 110 | var edgeHdrs map[int32][]byte 111 | 112 | if headers != nil { 113 | msgUUID = headers[xgress.HeaderKeyUUID] 114 | 115 | edgeHdrs = make(map[int32][]byte) 116 | for k, v := range headers { 117 | if edgeHeader, found := edgexg.HeadersFromFabric[k]; found { 118 | edgeHdrs[edgeHeader] = v 119 | } 120 | } 121 | } 122 | 123 | msg := edge.NewDataMsg(self.conn.Id(), bytes) 124 | if msgUUID != nil { 125 | msg.Headers[edge.UUIDHeader] = msgUUID 126 | } 127 | 128 | for k, v := range edgeHdrs { 129 | msg.Headers[k] = v 130 | } 131 | 132 | if err := self.conn.readQ.PutSequenced(msg); err != nil { 133 | logrus.WithFields(edge.GetLoggerFields(msg)).WithError(err). 134 | Error("error pushing edge message to sequencer") 135 | return 0, err 136 | } 137 | 138 | logrus.WithFields(edge.GetLoggerFields(msg)).Debugf("received %v bytes (msg type: %v)", len(msg.Body), msg.ContentType) 139 | return len(msg.Body), nil 140 | } 141 | 142 | func (self *XgAdapter) HandleControlMsg(controlType xgress.ControlType, headers channel.Headers, responder xgress.ControlReceiver) error { 143 | //TODO implement me 144 | panic("implement me") 145 | } 146 | -------------------------------------------------------------------------------- /ziti/edge/posture/domain.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | /* 4 | Copyright 2019 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package posture 20 | 21 | func Domain() string { 22 | return "" 23 | } 24 | -------------------------------------------------------------------------------- /ziti/edge/posture/domain_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | /* 4 | Copyright 2019 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package posture 20 | 21 | import ( 22 | "syscall" 23 | "unicode/utf16" 24 | "unsafe" 25 | ) 26 | 27 | func Domain() string { 28 | var domain *uint16 29 | var status uint32 30 | 31 | err := syscall.NetGetJoinInformation(nil, &domain, &status) 32 | if err != nil { 33 | return "" 34 | } 35 | defer syscall.NetApiBufferFree((*byte)(unsafe.Pointer(domain))) 36 | //todo: add this back in so that workgroups aren't allowed: status == syscall.NetSetupDomainName && 37 | if domain != nil { 38 | domainName := cstringTostring(domain) 39 | return domainName 40 | } 41 | 42 | return "" 43 | } 44 | 45 | func cstringTostring(cs *uint16) (s string) { 46 | if cs != nil { 47 | us := make([]uint16, 0, 256) 48 | for p := uintptr(unsafe.Pointer(cs)); ; p += 2 { 49 | u := *(*uint16)(unsafe.Pointer(p)) 50 | if u == 0 { 51 | return string(utf16.Decode(us)) 52 | } 53 | us = append(us, u) 54 | } 55 | } 56 | return "" 57 | } 58 | -------------------------------------------------------------------------------- /ziti/edge/posture/mac.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package posture 18 | 19 | import "net" 20 | 21 | func MacAddresses() []string { 22 | netInterfaces, err := net.Interfaces() 23 | if err != nil { 24 | return nil 25 | } 26 | var addresses []string 27 | for _, netInterface := range netInterfaces { 28 | a := netInterface.HardwareAddr.String() 29 | if a != "" { 30 | addresses = append(addresses, a) 31 | } 32 | } 33 | return addresses 34 | } 35 | -------------------------------------------------------------------------------- /ziti/edge/posture/os.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package posture 18 | 19 | import ( 20 | "regexp" 21 | "runtime" 22 | "strings" 23 | 24 | "github.com/shirou/gopsutil/v3/host" 25 | ) 26 | 27 | type OsInfo struct { 28 | Type string 29 | Version string 30 | } 31 | 32 | func Os() OsInfo { 33 | osType := runtime.GOOS 34 | osVersion := "unknown" 35 | 36 | semVerParser := regexp.MustCompile(`^((\d+)\.(\d+)\.(\d+))`) 37 | 38 | _, family, version, _ := host.PlatformInformation() 39 | 40 | if runtime.GOOS == "windows" { 41 | if strings.EqualFold(family, "server") { 42 | osType = "windowsserver" 43 | } else { 44 | osType = "windows" 45 | } 46 | } 47 | 48 | parsedVersion := semVerParser.FindStringSubmatch(version) 49 | 50 | if len(parsedVersion) > 1 { 51 | osVersion = parsedVersion[0] 52 | } 53 | return OsInfo{ 54 | Type: osType, 55 | Version: osVersion, 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /ziti/edge/posture/posture_windows_test.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | /* 4 | Copyright 2019 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | package posture 19 | 20 | import ( 21 | "testing" 22 | ) 23 | 24 | func TestRunningProcess(t *testing.T) { 25 | p := Process("C:\\Windows\\System32\\svchost.exe") 26 | if !p.IsRunning { 27 | t.Fail() 28 | } 29 | } 30 | 31 | func TestRunningProcessCaseInsensitive(t *testing.T) { 32 | p := Process("C:\\windows\\system32\\SVCHOST.EXE") 33 | if !p.IsRunning { 34 | t.Fail() 35 | } 36 | } 37 | 38 | func TestSlashNormalizationForwardSlash(t *testing.T) { 39 | p := Process("C:/windows/system32/SVCHOST.EXE") 40 | if !p.IsRunning { 41 | t.Fail() 42 | } 43 | } 44 | 45 | func TestSlashNormalizationExtraSlashes(t *testing.T) { 46 | p := Process("C:/windows///system32////SVCHOST.EXE") 47 | if !p.IsRunning { 48 | t.Fail() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /ziti/edge/posture/process.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package posture 18 | 19 | type ProcessInfo struct { 20 | IsRunning bool 21 | Hash string 22 | SignerFingerprints []string 23 | } 24 | -------------------------------------------------------------------------------- /ziti/edge/posture/process_js.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package posture 18 | 19 | func Process(providedPath string) ProcessInfo { 20 | return ProcessInfo{ 21 | IsRunning: false, 22 | Hash: "", 23 | SignerFingerprints: nil, 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ziti/edge/posture/process_notjs.go: -------------------------------------------------------------------------------- 1 | //go:build !js 2 | 3 | /* 4 | Copyright 2019 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package posture 20 | 21 | import ( 22 | "crypto/sha512" 23 | "fmt" 24 | "github.com/michaelquigley/pfxlog" 25 | "github.com/mitchellh/go-ps" 26 | "github.com/shirou/gopsutil/v3/process" 27 | "os" 28 | "path/filepath" 29 | "strings" 30 | ) 31 | 32 | func Process(providedPath string) ProcessInfo { 33 | expectedPath := filepath.Clean(providedPath) 34 | 35 | processes, err := ps.Processes() 36 | 37 | if err != nil { 38 | pfxlog.Logger().Debugf("error getting Processes: %v", err) 39 | } 40 | 41 | if len(processes) == 0 { 42 | pfxlog.Logger().Warnf("total processes found was zero, this is unexpected") 43 | } 44 | 45 | for _, proc := range processes { 46 | if !isProcessPath(expectedPath, proc.Executable()) { 47 | continue 48 | } 49 | 50 | procDetails, err := process.NewProcess(int32(proc.Pid())) 51 | 52 | if err != nil { 53 | continue 54 | } 55 | 56 | executablePath, err := procDetails.Exe() 57 | 58 | if err != nil { 59 | continue 60 | } 61 | 62 | if strings.EqualFold(executablePath, expectedPath) { 63 | isRunning, _ := procDetails.IsRunning() 64 | file, err := os.ReadFile(executablePath) 65 | 66 | if err != nil { 67 | pfxlog.Logger().Warnf("could not read process executable file: %v", err) 68 | return ProcessInfo{ 69 | IsRunning: isRunning, 70 | Hash: "", 71 | SignerFingerprints: nil, 72 | } 73 | } 74 | 75 | sum := sha512.Sum512(file) 76 | hash := fmt.Sprintf("%x", sum[:]) 77 | 78 | signerFingerprints, err := getSignerFingerprints(executablePath) 79 | 80 | if err != nil { 81 | pfxlog.Logger().Warnf("could not read process signatures: %v", err) 82 | return ProcessInfo{ 83 | IsRunning: isRunning, 84 | Hash: hash, 85 | SignerFingerprints: nil, 86 | } 87 | } 88 | 89 | return ProcessInfo{ 90 | IsRunning: isRunning, 91 | Hash: hash, 92 | SignerFingerprints: signerFingerprints, 93 | } 94 | } 95 | } 96 | 97 | return ProcessInfo{ 98 | IsRunning: false, 99 | Hash: "", 100 | SignerFingerprints: nil, 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /ziti/edge/posture/process_notwin.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | /* 4 | Copyright 2019 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package posture 20 | 21 | import "strings" 22 | 23 | func isProcessPath(expectedPath, processPath string) bool { 24 | return strings.Contains(expectedPath, processPath) 25 | } 26 | 27 | func getSignerFingerprints(filePath string) ([]string, error) { 28 | return nil, nil 29 | } 30 | -------------------------------------------------------------------------------- /ziti/edge/posture/process_windows.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | /* 4 | Copyright 2019 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package posture 20 | 21 | import ( 22 | "crypto/sha1" 23 | "debug/pe" 24 | "fmt" 25 | "go.mozilla.org/pkcs7" 26 | "os" 27 | "strings" 28 | ) 29 | 30 | func isProcessPath(expectedPath, processPath string) bool { 31 | return strings.Contains(strings.ToLower(expectedPath), strings.ToLower(processPath)) 32 | } 33 | 34 | func getSignerFingerprints(filePath string) ([]string, error) { 35 | peFile, err := pe.Open(filePath) 36 | 37 | if err != nil { 38 | return nil, err 39 | } 40 | defer peFile.Close() 41 | 42 | var virtualAddress uint32 43 | var size uint32 44 | switch t := peFile.OptionalHeader.(type) { 45 | case *pe.OptionalHeader32: 46 | virtualAddress = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress 47 | size = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].Size 48 | case *pe.OptionalHeader64: 49 | virtualAddress = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress 50 | size = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].Size 51 | } 52 | 53 | if virtualAddress <= 0 || size <= 0 { 54 | return nil, nil 55 | } 56 | 57 | binaryFile, err := os.Open(filePath) 58 | 59 | if err != nil { 60 | return nil, fmt.Errorf("failed to open process file: %v", err) 61 | } 62 | defer binaryFile.Close() 63 | 64 | pkcs7Signature := make([]byte, int64(size)) 65 | _, _ = binaryFile.ReadAt(pkcs7Signature, int64(virtualAddress+8)) 66 | 67 | pkcs7Obj, err := pkcs7.Parse(pkcs7Signature) 68 | 69 | if err != nil { 70 | return nil, fmt.Errorf("failed to parse pkcs7 block: %v", err) 71 | } 72 | var signerFingerprints []string 73 | for _, cert := range pkcs7Obj.Certificates { 74 | signerFingerprint := fmt.Sprintf("%x", sha1.Sum(cert.Raw)) 75 | signerFingerprints = append(signerFingerprints, signerFingerprint) 76 | } 77 | 78 | return signerFingerprints, nil 79 | } 80 | -------------------------------------------------------------------------------- /ziti/key_alg_var.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ziti 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "strings" 23 | ) 24 | 25 | type KeyAlgVar string 26 | 27 | func (f *KeyAlgVar) String() string { 28 | return fmt.Sprint(string(*f)) 29 | } 30 | 31 | func (f *KeyAlgVar) Set(value string) error { 32 | value = strings.ToUpper(value) 33 | if value != "EC" && value != "RSA" { 34 | return errors.New("invalid option -- must specify either 'EC' or 'RSA'") 35 | } 36 | *f = KeyAlgVar(value) 37 | return nil 38 | } 39 | 40 | func (f *KeyAlgVar) EC() bool { 41 | return f.Get() == "EC" 42 | } 43 | 44 | func (f *KeyAlgVar) RSA() bool { 45 | return f.Get() == "RSA" 46 | } 47 | 48 | func (f *KeyAlgVar) Get() string { 49 | return string(*f) 50 | } 51 | 52 | func (f *KeyAlgVar) Type() string { 53 | return "RSA|EC" 54 | } 55 | -------------------------------------------------------------------------------- /ziti/sdkinfo/build_info.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright NetFoundry Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | // Code generated by ziti-ci. DO NOT EDIT. 19 | 20 | package sdkinfo 21 | 22 | const ( 23 | Version = "v1.1.2" 24 | ) 25 | -------------------------------------------------------------------------------- /ziti/sdkinfo/host.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sdkinfo 18 | 19 | import ( 20 | "github.com/openziti/edge-api/rest_model" 21 | "github.com/sirupsen/logrus" 22 | "runtime" 23 | ) 24 | 25 | var appId string 26 | var appVersion string 27 | 28 | func SetApplication(theAppId, theAppVersion string) { 29 | appId = theAppId 30 | appVersion = theAppVersion 31 | } 32 | 33 | func GetSdkInfo() (*rest_model.EnvInfo, *rest_model.SdkInfo) { 34 | sdkInfo := &rest_model.SdkInfo{ 35 | AppID: appId, 36 | AppVersion: appVersion, 37 | Type: "ziti-sdk-golang", 38 | Version: Version, 39 | } 40 | 41 | envInfo := &rest_model.EnvInfo{ 42 | Arch: runtime.GOARCH, 43 | Os: runtime.GOOS, 44 | OsRelease: "", 45 | OsVersion: "", 46 | } 47 | 48 | if rel, ver, err := getOSversion(); err == nil { 49 | envInfo.OsRelease = rel 50 | envInfo.OsVersion = ver 51 | } else { 52 | logrus.Warn("failed to get OS version", err) 53 | } 54 | 55 | return envInfo, sdkInfo 56 | 57 | } 58 | -------------------------------------------------------------------------------- /ziti/sdkinfo/host_js.go: -------------------------------------------------------------------------------- 1 | //go:build js 2 | 3 | /* 4 | Copyright 2020 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package sdkinfo 20 | 21 | func getOSversion() (string, string, error) { 22 | return "javascript", "unknown", nil 23 | } 24 | -------------------------------------------------------------------------------- /ziti/sdkinfo/host_unix.go: -------------------------------------------------------------------------------- 1 | //go:build unix 2 | 3 | /* 4 | Copyright 2020 NetFoundry Inc. 5 | 6 | Licensed under the Apache License, Version 2.0 (the "License"); 7 | you may not use this file except in compliance with the License. 8 | You may obtain a copy of the License at 9 | 10 | https://www.apache.org/licenses/LICENSE-2.0 11 | 12 | Unless required by applicable law or agreed to in writing, software 13 | distributed under the License is distributed on an "AS IS" BASIS, 14 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | See the License for the specific language governing permissions and 16 | limitations under the License. 17 | */ 18 | 19 | package sdkinfo 20 | 21 | import ( 22 | "golang.org/x/sys/unix" 23 | "strings" 24 | ) 25 | 26 | var nullTerm = string([]byte{0}) 27 | 28 | // get string out of \0-terminated bytes 29 | func toString(b []byte) string { 30 | s := strings.SplitN(string(b), nullTerm, 2) 31 | return s[0] 32 | } 33 | 34 | func getOSversion() (string, string, error) { 35 | osInfo := new(unix.Utsname) 36 | if err := unix.Uname(osInfo); err == nil { 37 | return toString(osInfo.Release[:]), toString(osInfo.Version[:]), nil 38 | } else { 39 | return "", "", err 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /ziti/sdkinfo/host_windows.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2020 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package sdkinfo 18 | 19 | import ( 20 | "fmt" 21 | "syscall" 22 | ) 23 | 24 | func getOSversion() (string, string, error) { 25 | if ver, err := syscall.GetVersion(); err == nil { 26 | major := ver & 0xff 27 | minor := (ver >> 8) & 0xff 28 | buildnum := (ver >> 16) 29 | 30 | rel := fmt.Sprintf("%d.%d.%d", major, minor, buildnum) 31 | return rel, rel, nil 32 | } else { 33 | return "", "", err 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ziti/signing/signing_test.go: -------------------------------------------------------------------------------- 1 | package signing 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/elliptic" 6 | "crypto/rand" 7 | "crypto/rsa" 8 | "github.com/stretchr/testify/require" 9 | "testing" 10 | ) 11 | 12 | func Test_SignAndVerifyRsa(t *testing.T) { 13 | req := require.New(t) 14 | key, err := rsa.GenerateKey(rand.Reader, 4096) 15 | req.NoError(err) 16 | testKeyPair(t, key, key.Public()) 17 | } 18 | 19 | func Test_SignAndVerifyEcdsa(t *testing.T) { 20 | req := require.New(t) 21 | key, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader) 22 | 23 | req.NoError(err) 24 | testKeyPair(t, key, key.Public()) 25 | } 26 | 27 | func testKeyPair(t *testing.T, privateKey interface{}, publicKey interface{}) { 28 | req := require.New(t) 29 | sig, err := AssertIdentityWithSecret(privateKey) 30 | req.NoError(err) 31 | 32 | verifier, err := GetVerifier(sig) 33 | req.NoError(err) 34 | req.True(verifier.Verify(publicKey)) 35 | } 36 | -------------------------------------------------------------------------------- /ziti/terminators.go: -------------------------------------------------------------------------------- 1 | package ziti 2 | 3 | import "github.com/openziti/edge-api/rest_model" 4 | 5 | type Precedence byte 6 | 7 | func (p Precedence) String() string { 8 | if p == PrecedenceRequired { 9 | return PrecedenceRequiredLabel 10 | } 11 | if p == PrecedenceFailed { 12 | return PrecedenceFailedLabel 13 | } 14 | return PrecedenceDefaultLabel 15 | } 16 | 17 | const ( 18 | PrecedenceDefault Precedence = 0 19 | PrecedenceRequired Precedence = 1 20 | PrecedenceFailed Precedence = 2 21 | 22 | PrecedenceDefaultLabel = string(rest_model.TerminatorPrecedenceDefault) 23 | PrecedenceRequiredLabel = string(rest_model.TerminatorPrecedenceRequired) 24 | PrecedenceFailedLabel = string(rest_model.TerminatorPrecedenceFailed) 25 | ) 26 | 27 | func GetPrecedenceForLabel(p string) Precedence { 28 | if p == PrecedenceRequiredLabel { 29 | return PrecedenceRequired 30 | } 31 | if p == PrecedenceFailedLabel { 32 | return PrecedenceFailed 33 | } 34 | return PrecedenceDefault 35 | } 36 | -------------------------------------------------------------------------------- /ziti/token.go: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2019 NetFoundry Inc. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | https://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | package ziti 18 | 19 | import ( 20 | "crypto/x509" 21 | "github.com/golang-jwt/jwt/v5" 22 | "github.com/michaelquigley/pfxlog" 23 | "net/url" 24 | ) 25 | 26 | var EnrollUrl, _ = url.Parse("/edge/client/v1/enroll") 27 | 28 | const EnrollmentMethodCa = "ca" 29 | 30 | type Versions struct { 31 | Api string `json:"api"` 32 | EnrollmentApi string `json:"enrollmentApi"` 33 | } 34 | 35 | type EnrollmentClaims struct { 36 | jwt.RegisteredClaims 37 | EnrollmentMethod string `json:"em"` 38 | Controllers []string `json:"ctrls"` 39 | SignatureCert *x509.Certificate `json:"-"` 40 | } 41 | 42 | func (t *EnrollmentClaims) EnrolmentUrl() string { 43 | enrollmentUrl, err := url.Parse(t.Issuer) 44 | 45 | if err != nil { 46 | pfxlog.Logger().WithError(err).WithField("url", t.Issuer).Panic("could not parse issuer as URL") 47 | } 48 | 49 | enrollmentUrl = enrollmentUrl.ResolveReference(EnrollUrl) 50 | 51 | query := enrollmentUrl.Query() 52 | query.Add("method", t.EnrollmentMethod) 53 | 54 | if t.EnrollmentMethod != EnrollmentMethodCa { 55 | query.Add("token", t.ID) 56 | } 57 | 58 | enrollmentUrl.RawQuery = query.Encode() 59 | 60 | return enrollmentUrl.String() 61 | } 62 | -------------------------------------------------------------------------------- /ziti/xg_env.go: -------------------------------------------------------------------------------- 1 | package ziti 2 | 3 | import ( 4 | "github.com/openziti/metrics" 5 | "github.com/openziti/sdk-golang/xgress" 6 | ) 7 | 8 | type xgEnv struct { 9 | retransmitter *xgress.Retransmitter 10 | payloadIngester *xgress.PayloadIngester 11 | metrics xgress.Metrics 12 | } 13 | 14 | func NewXgressEnv(closeNotify <-chan struct{}, registry metrics.Registry) xgress.Env { 15 | return &xgEnv{ 16 | retransmitter: xgress.NewRetransmitter(dummyRetransmitterFaultReporter{}, registry, closeNotify), 17 | payloadIngester: xgress.NewPayloadIngester(closeNotify), 18 | metrics: xgress.NewMetrics(registry), 19 | } 20 | } 21 | 22 | func (x xgEnv) GetRetransmitter() *xgress.Retransmitter { 23 | return x.retransmitter 24 | } 25 | 26 | func (x xgEnv) GetPayloadIngester() *xgress.PayloadIngester { 27 | return x.payloadIngester 28 | } 29 | 30 | func (x xgEnv) GetMetrics() xgress.Metrics { 31 | return x.metrics 32 | } 33 | 34 | type dummyRetransmitterFaultReporter struct{} 35 | 36 | func (d dummyRetransmitterFaultReporter) ReportForwardingFault(circuitId string, ctrlId string) { 37 | // the only way to get a fault is if the connection goes down, in which case the circuit will 38 | // get torn down anyway 39 | } 40 | --------------------------------------------------------------------------------