├── version ├── .gitignore ├── assets └── Go SDK.png ├── example ├── curlz │ ├── zitified.png │ ├── unzitified.png │ └── curlz.go ├── zping │ ├── network.png │ ├── main.go │ ├── root.go │ ├── server.go │ └── enroll.go ├── reflect │ ├── cmd │ │ ├── pkg-vars.go │ │ ├── server.go │ │ └── client.go │ ├── docker-compose.yml │ ├── Dockerfile │ ├── main.go │ └── README.md ├── device-auth │ ├── README.md │ └── main.go ├── jwtchat │ ├── jwtchat-idp │ │ ├── storage │ │ │ ├── token.go │ │ │ └── user.go │ │ ├── main.go │ │ └── exampleop │ │ │ ├── login.go │ │ │ └── op.go │ ├── README.md │ ├── jwtchat-client │ │ └── client.go │ └── jwtchat-server │ │ └── server.go ├── udp-offload │ ├── udp-server │ │ └── udp-server.go │ ├── udp-offload-client │ │ └── main.go │ └── README.md ├── http-client │ ├── main.go │ └── README.md ├── grpc-example │ ├── grpc-server │ │ └── main.go │ ├── grpc-client │ │ └── main.go │ └── README.md ├── README.md ├── chat │ ├── chat-client │ │ └── chat-client.go │ ├── README.md │ └── chat-server │ │ └── chat-server.go ├── chat-p2p │ ├── setup.md │ └── setup.go ├── simple-server │ └── simple-server.go ├── influxdb-client-go │ └── main.go └── zcat │ └── zcat.go ├── expected.licenses ├── exercises ├── http │ ├── simple-cloud.png │ ├── simple-example.png │ ├── simple-zitified-example.png │ ├── client │ │ ├── before │ │ │ └── simple-client.go │ │ └── zitified │ │ │ └── simple-client.go │ ├── server │ │ ├── before │ │ │ └── simple-server.go │ │ └── zitified │ │ │ └── simple-server.go │ └── README.md └── README.md ├── .golangci.yml ├── .github ├── CODEOWNERS ├── dependabot.yml └── workflows │ ├── mattermost-webhook.yml │ ├── main.yml │ ├── release.yml │ ├── golangci-lint.yml │ └── mattermost-channel-posts.yml ├── pb └── edge_client_pb │ ├── generate.go │ └── README.md ├── SECURITY.md ├── inspect └── inspect.go ├── ziti ├── edge │ ├── posture │ │ ├── eventstates_windows.go │ │ ├── eventstates_notwindows.go │ │ ├── domain_notwindows.go │ │ ├── process_notwin.go │ │ ├── process_js.go │ │ ├── domain.go │ │ ├── posture_windows_test.go │ │ ├── process.go │ │ ├── eventstates.go │ │ ├── mac.go │ │ ├── domain_windows.go │ │ ├── os.go │ │ ├── process_windows.go │ │ └── process_notjs.go │ ├── addr_parsers.go │ ├── addr_parsers_js.go │ ├── addr.go │ └── network │ │ ├── seq_test.go │ │ ├── seq.go │ │ ├── msg_timer.go │ │ └── xg_adapter.go ├── sdkinfo │ ├── host_js.go │ ├── build_info.go │ ├── host_windows.go │ ├── host_unix.go │ └── host.go ├── signing │ └── signing_test.go ├── terminators.go ├── xg_env.go ├── key_alg_var.go ├── token.go ├── default_collection.go └── dialer.go ├── sdk-version └── sdk-version.go ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── edgexg └── impl.go ├── license-check.sh ├── edge-apis ├── api_session_test.go ├── urls.go └── oidc.go ├── RELEASING.md ├── .gitattributes ├── xgress ├── heartbeat_transformer.go ├── payload_ingester.go ├── messages_test.go ├── decoder.go ├── link_receive_buffer.go ├── ordering_test.go ├── metrics.go └── circuit_inspections.go ├── http_transport.go └── go.mod /version: -------------------------------------------------------------------------------- 1 | 1.3 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | github_deploy_key 3 | *.json 4 | *.jwt 5 | go.work 6 | go.work.sum 7 | -------------------------------------------------------------------------------- /assets/Go SDK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/HEAD/assets/Go SDK.png -------------------------------------------------------------------------------- /example/curlz/zitified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/HEAD/example/curlz/zitified.png -------------------------------------------------------------------------------- /example/zping/network.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/HEAD/example/zping/network.png -------------------------------------------------------------------------------- /example/curlz/unzitified.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/HEAD/example/curlz/unzitified.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 | -------------------------------------------------------------------------------- /exercises/http/simple-cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/HEAD/exercises/http/simple-cloud.png -------------------------------------------------------------------------------- /exercises/http/simple-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/HEAD/exercises/http/simple-example.png -------------------------------------------------------------------------------- /example/reflect/cmd/pkg-vars.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "github.com/michaelquigley/pfxlog" 4 | 5 | var log = pfxlog.Logger() 6 | -------------------------------------------------------------------------------- /exercises/http/simple-zitified-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openziti/sdk-golang/HEAD/exercises/http/simple-zitified-example.png -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | linters: 3 | settings: 4 | staticcheck: 5 | checks: [ "all", "-ST1000", "-ST1003", "-ST1006", "-ST1020", "-ST1021" ] 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ziti/edge/posture/eventstates_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package posture 4 | 5 | // NewEventState is a stand-in for actual Window event watching 6 | func NewEventState() EventState { 7 | return &NoOpEventState{} 8 | } 9 | -------------------------------------------------------------------------------- /ziti/edge/posture/eventstates_notwindows.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package posture 4 | 5 | // NewEventState is a stand-in for actual non-Windows event watching 6 | func NewEventState() EventState { 7 | return &NoOpEventState{} 8 | } 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /example/device-auth/README.md: -------------------------------------------------------------------------------- 1 | OIDC Device Code authentication example 2 | --- 3 | 4 | This sample shows OpenZiti OIDC authentication with device code flow. 5 | Prerequisites: 6 | - your OpenZiti network is configured with an external OIDC provider 7 | - your OIDC provider is configured to allow device code flow -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // HeadersToFabric tracks the 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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/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/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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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.3.1" 24 | ) 25 | -------------------------------------------------------------------------------- /.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@v5 17 | with: 18 | persist-credentials: false 19 | 20 | - name: Install Go 21 | uses: actions/setup-go@v6 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 | -------------------------------------------------------------------------------- /edge-apis/api_session_test.go: -------------------------------------------------------------------------------- 1 | package edge_apis 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func Test_ApiSessionMarshalling(t *testing.T) { 11 | 12 | t.Run("test marshalling", func(t *testing.T) { 13 | req := require.New(t) 14 | type testStruct struct { 15 | ApiSession ApiSessionJsonWrapper `json:"apiSession"` 16 | } 17 | 18 | test := &testStruct{ 19 | ApiSession: ApiSessionJsonWrapper{ 20 | ApiSession: NewApiSessionOidc("access", "refresh"), 21 | }, 22 | } 23 | 24 | testJson, err := json.Marshal(test) 25 | req.NoError(err) 26 | 27 | testUnmarhsal := &testStruct{} 28 | 29 | err = json.Unmarshal(testJson, testUnmarhsal) 30 | req.NoError(err) 31 | req.Equal(test.ApiSession.ApiSession.GetToken(), testUnmarhsal.ApiSession.ApiSession.GetToken()) 32 | }) 33 | } 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ziti/edge/posture/domain_notwindows.go: -------------------------------------------------------------------------------- 1 | //go: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 NewDomainProvider() DomainProvider { 22 | return &EmptyDomainProvider{} 23 | } 24 | 25 | type EmptyDomainProvider struct{} 26 | 27 | func (p *EmptyDomainProvider) GetDomain() string { 28 | return "" 29 | } 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ziti/edge/posture/process_notwin.go: -------------------------------------------------------------------------------- 1 | //go: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 | -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /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/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/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 NewProcessProvider() ProcessProvider { 20 | return &EmptyProcessProvider{} 21 | } 22 | 23 | type EmptyProcessProvider struct{} 24 | 25 | func (p *EmptyProcessProvider) GetProcessInfo(providedPath string) ProcessInfo { 26 | return ProcessInfo{ 27 | IsRunning: false, 28 | Hash: "", 29 | SignerFingerprints: nil, 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /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/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/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.NewPayloadIngesterWithConfig(5, 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 | -------------------------------------------------------------------------------- /.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@v5 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Install Go 19 | uses: actions/setup-go@v6 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ziti/edge/posture/domain.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 | // DomainProvider supplies the Windows domain name that the device is joined to, 20 | // used for domain membership posture checks. 21 | type DomainProvider interface { 22 | GetDomain() string 23 | } 24 | 25 | // DomainFuncAsProvider converts a simple domain-returning function into a DomainProvider. 26 | func DomainFuncAsProvider(f func() string) DomainProvider { 27 | return DomainProviderFunc(f) 28 | } 29 | 30 | // DomainProviderFunc is a function adapter that implements DomainProvider. 31 | type DomainProviderFunc func() string 32 | 33 | func (f DomainProviderFunc) GetDomain() string { 34 | return f() 35 | } 36 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/edge/posture/posture_windows_test.go: -------------------------------------------------------------------------------- 1 | //go: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 | "testing" 23 | ) 24 | 25 | func TestRunningProcess(t *testing.T) { 26 | p := Process("C:\\Windows\\System32\\svchost.exe") 27 | if !p.IsRunning { 28 | t.Fail() 29 | } 30 | } 31 | 32 | func TestRunningProcessCaseInsensitive(t *testing.T) { 33 | p := Process("C:\\windows\\system32\\SVCHOST.EXE") 34 | if !p.IsRunning { 35 | t.Fail() 36 | } 37 | } 38 | 39 | func TestSlashNormalizationForwardSlash(t *testing.T) { 40 | p := Process("C:/windows/system32/SVCHOST.EXE") 41 | if !p.IsRunning { 42 | t.Fail() 43 | } 44 | } 45 | 46 | func TestSlashNormalizationExtraSlashes(t *testing.T) { 47 | p := Process("C:/windows///system32////SVCHOST.EXE") 48 | if !p.IsRunning { 49 | t.Fail() 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // ProcessProvider supplies information about specific processes running on the device, 20 | // including execution state, binary hash, and code signing details for process posture checks. 21 | type ProcessProvider interface { 22 | GetProcessInfo(path string) ProcessInfo 23 | } 24 | 25 | // ProcessInfo contains details about a specific process including whether it's running, 26 | // its binary hash, and code signing fingerprints. 27 | type ProcessInfo struct { 28 | IsRunning bool 29 | Hash string 30 | SignerFingerprints []string 31 | QueryId string 32 | } 33 | 34 | // ProcessInfoFunc is a function adapter that implements ProcessProvider. 35 | type ProcessInfoFunc func(path string) ProcessInfo 36 | 37 | func (f ProcessInfoFunc) GetProcessInfo(path string) ProcessInfo { 38 | return f(path) 39 | } 40 | -------------------------------------------------------------------------------- /xgress/payload_ingester.go: -------------------------------------------------------------------------------- 1 | package xgress 2 | 3 | import ( 4 | "fmt" 5 | "github.com/michaelquigley/pfxlog" 6 | "github.com/openziti/foundation/v2/goroutines" 7 | "github.com/sirupsen/logrus" 8 | "runtime/debug" 9 | "time" 10 | ) 11 | 12 | type PayloadIngester struct { 13 | pool goroutines.Pool 14 | } 15 | 16 | func NewPayloadIngester(closeNotify <-chan struct{}) *PayloadIngester { 17 | return NewPayloadIngesterWithConfig(1, closeNotify) 18 | } 19 | 20 | func NewPayloadIngesterWithConfig(maxWorkers uint32, closeNotify <-chan struct{}) *PayloadIngester { 21 | if maxWorkers < 1 { 22 | maxWorkers = 1 23 | } 24 | poolConfig := goroutines.PoolConfig{ 25 | QueueSize: uint32(64), 26 | MinWorkers: 1, 27 | MaxWorkers: maxWorkers, 28 | IdleTime: 30 * time.Second, 29 | CloseNotify: closeNotify, 30 | PanicHandler: func(err interface{}) { 31 | pfxlog.Logger().WithField(logrus.ErrorKey, err).WithField("backtrace", string(debug.Stack())).Error("panic during payload ingest") 32 | }, 33 | WorkerFunction: payloadIngesterWorker, 34 | } 35 | 36 | pool, err := goroutines.NewPool(poolConfig) 37 | if err != nil { 38 | panic(fmt.Errorf("error creating payload ingester handler pool (%w)", err)) 39 | } 40 | 41 | pi := &PayloadIngester{ 42 | pool: pool, 43 | } 44 | 45 | return pi 46 | } 47 | 48 | func payloadIngesterWorker(_ uint32, f func()) { 49 | f() 50 | } 51 | 52 | func (self *PayloadIngester) ingest(payload *Payload, x *Xgress) { 53 | _ = self.pool.Queue(func() { 54 | x.acceptPayload(payload) 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.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@v6 15 | with: 16 | go-version: stable 17 | - uses: actions/checkout@v5 18 | - name: golangci-lint 19 | uses: golangci/golangci-lint-action@v8 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /ziti/edge/posture/eventstates.go: -------------------------------------------------------------------------------- 1 | package posture 2 | 3 | import "time" 4 | 5 | // WakeEvent represents a device wake event from sleep or hibernation, used to trigger 6 | // posture re-evaluation when the system resumes from a suspended state. 7 | type WakeEvent struct { 8 | At time.Time 9 | } 10 | 11 | // UnlockEvent represents a device unlock event after screen lock, used to trigger 12 | // posture re-evaluation when user authentication state changes. 13 | type UnlockEvent struct { 14 | At time.Time 15 | } 16 | 17 | // EventState provides platform-specific monitoring of system events that may affect 18 | // posture compliance, such as waking from sleep or unlocking the device. 19 | type EventState interface { 20 | // ListenForWake registers a callback for system wake events, returning a function 21 | // to stop listening. 22 | ListenForWake(func(WakeEvent)) (stop func(), err error) 23 | 24 | // ListenForUnlock registers a callback for device unlock events, returning a function 25 | // to stop listening. 26 | ListenForUnlock(func(event UnlockEvent)) (stop func(), err error) 27 | } 28 | 29 | var _ EventState = (*NoOpEventState)(nil) 30 | 31 | // NoOpEventState is a placeholder implementation that stores callbacks without actually 32 | // monitoring system events. Platform-specific implementations should be used for 33 | // production deployments. 34 | type NoOpEventState struct { 35 | onWake func(WakeEvent) 36 | onUnlock func(UnlockEvent) 37 | } 38 | 39 | func (n *NoOpEventState) ListenForWake(f func(WakeEvent)) (stop func(), err error) { 40 | n.onWake = f 41 | return func() { 42 | n.onWake = nil 43 | }, nil 44 | } 45 | 46 | func (n *NoOpEventState) ListenForUnlock(f func(event UnlockEvent)) (stop func(), err error) { 47 | n.onUnlock = f 48 | return func() { 49 | n.onUnlock = nil 50 | }, nil 51 | } 52 | -------------------------------------------------------------------------------- /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 | // MacProvider supplies the list of MAC addresses for network interfaces on the device, 22 | // used for MAC address posture checks. 23 | type MacProvider interface { 24 | GetMacAddresses() []string 25 | } 26 | 27 | // MacProviderFunc is a function adapter that implements MacProvider. 28 | type MacProviderFunc func() []string 29 | 30 | func (f MacProviderFunc) GetMacAddresses() []string { 31 | return f() 32 | } 33 | 34 | // NewMacProvider creates the default MAC address provider that queries system network interfaces. 35 | func NewMacProvider() MacProvider { 36 | return &DefaultMacProvider{} 37 | } 38 | 39 | // DefaultMacProvider queries the system's network interfaces to collect MAC addresses. 40 | type DefaultMacProvider struct{} 41 | 42 | func (p *DefaultMacProvider) GetMacAddresses() []string { 43 | netInterfaces, err := net.Interfaces() 44 | if err != nil { 45 | return nil 46 | } 47 | var addresses []string 48 | for _, netInterface := range netInterfaces { 49 | a := netInterface.HardwareAddr.String() 50 | if a != "" { 51 | addresses = append(addresses, a) 52 | } 53 | } 54 | return addresses 55 | } 56 | -------------------------------------------------------------------------------- /ziti/edge/posture/domain_windows.go: -------------------------------------------------------------------------------- 1 | //go: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 NewDomainProvider() DomainProvider { 28 | return &WindowsDomainProvider{} 29 | } 30 | 31 | type WindowsDomainProvider struct{} 32 | 33 | func (p *WindowsDomainProvider) GetDomain() string { 34 | var domain *uint16 35 | var status uint32 36 | 37 | err := syscall.NetGetJoinInformation(nil, &domain, &status) 38 | if err != nil { 39 | return "" 40 | } 41 | defer func(buf *byte) { 42 | _ = syscall.NetApiBufferFree(buf) 43 | }((*byte)(unsafe.Pointer(domain))) 44 | 45 | //todo: add this back in so that workgroups aren't allowed: status == syscall.NetSetupDomainName && 46 | if domain != nil { 47 | domainName := cStringTostring(domain) 48 | return domainName 49 | } 50 | 51 | return "" 52 | } 53 | 54 | func cStringTostring(cs *uint16) (s string) { 55 | if cs != nil { 56 | us := make([]uint16, 0, 256) 57 | for p := uintptr(unsafe.Pointer(cs)); ; p += 2 { 58 | u := *(*uint16)(unsafe.Pointer(p)) 59 | if u == 0 { 60 | return string(utf16.Decode(us)) 61 | } 62 | us = append(us, u) 63 | } 64 | } 65 | return "" 66 | } 67 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/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 | -------------------------------------------------------------------------------- /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 | // OsProvider supplies operating system type and version information for OS posture checks. 28 | type OsProvider interface { 29 | GetOsInfo() OsInfo 30 | } 31 | 32 | // OsInfo contains the operating system type and semantic version. 33 | type OsInfo struct { 34 | Type string 35 | Version string 36 | } 37 | 38 | // OsProviderFunc is a function adapter that implements OsProvider. 39 | type OsProviderFunc func() OsInfo 40 | 41 | func (f OsProviderFunc) GetOsInfo() OsInfo { 42 | return f() 43 | } 44 | 45 | // NewOsProvider creates the default OS information provider that queries system details. 46 | func NewOsProvider() OsProvider { 47 | return &DefaultOsProvider{} 48 | } 49 | 50 | // DefaultOsProvider queries platform information to determine OS type and version. 51 | type DefaultOsProvider struct{} 52 | 53 | func (provider DefaultOsProvider) GetOsInfo() OsInfo { 54 | osType := runtime.GOOS 55 | osVersion := "unknown" 56 | 57 | semVerParser := regexp.MustCompile(`^((\d+)\.(\d+)\.(\d+))`) 58 | 59 | _, family, version, _ := host.PlatformInformation() 60 | 61 | if runtime.GOOS == "windows" { 62 | if strings.EqualFold(family, "server") { 63 | osType = "windowsserver" 64 | } else { 65 | osType = "windows" 66 | } 67 | } 68 | 69 | parsedVersion := semVerParser.FindStringSubmatch(version) 70 | 71 | if len(parsedVersion) > 1 { 72 | osVersion = parsedVersion[0] 73 | } 74 | return OsInfo{ 75 | Type: osType, 76 | Version: osVersion, 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /edge-apis/oidc.go: -------------------------------------------------------------------------------- 1 | package edge_apis 2 | 3 | import ( 4 | "github.com/golang-jwt/jwt/v5" 5 | "github.com/zitadel/oidc/v3/pkg/oidc" 6 | ) 7 | 8 | // JwtTokenPrefix is the standard prefix for JWT tokens, representing the first two characters 9 | // of a Base64URL-encoded JWT header. This prefix is used to identify JWT-format tokens. 10 | const JwtTokenPrefix = "ey" 11 | 12 | // ServiceAccessClaims represents the JWT claims for service-level access tokens, including 13 | // identity and session binding information specific to a service connection. 14 | type ServiceAccessClaims struct { 15 | jwt.RegisteredClaims 16 | ApiSessionId string `json:"z_asid"` 17 | IdentityId string `json:"z_iid"` 18 | TokenType string `json:"z_t"` 19 | Type string `json:"z_st"` 20 | } 21 | 22 | // ApiAccessClaims represents the JWT claims for API session access tokens, including 23 | // identity attributes, administrative status, and configuration bindings. 24 | type ApiAccessClaims struct { 25 | jwt.RegisteredClaims 26 | ApiSessionId string `json:"z_asid,omitempty"` 27 | ExternalId string `json:"z_eid,omitempty"` 28 | IsAdmin bool `json:"z_ia,omitempty"` 29 | ConfigTypes []string `json:"z_ct,omitempty"` 30 | ApplicationId string `json:"z_aid,omitempty"` 31 | Type string `json:"z_t"` 32 | CertFingerprints []string `json:"z_cfs"` 33 | Scopes []string `json:"scopes,omitempty"` 34 | } 35 | 36 | var _ jwt.Claims = (*IdClaims)(nil) 37 | 38 | // IdClaims wraps oidc.IDToken claims to fulfill the jwt.Claims interface 39 | type IdClaims struct { 40 | oidc.IDTokenClaims 41 | } 42 | 43 | func (r *IdClaims) GetExpirationTime() (*jwt.NumericDate, error) { 44 | return &jwt.NumericDate{Time: r.GetExpiration()}, nil 45 | } 46 | 47 | func (r *IdClaims) GetNotBefore() (*jwt.NumericDate, error) { 48 | notBefore := r.NotBefore.AsTime() 49 | return &jwt.NumericDate{Time: notBefore}, nil 50 | } 51 | 52 | func (r *IdClaims) GetIssuedAt() (*jwt.NumericDate, error) { 53 | return &jwt.NumericDate{Time: r.TokenClaims.GetIssuedAt()}, nil 54 | } 55 | 56 | func (r *IdClaims) GetIssuer() (string, error) { 57 | return r.Issuer, nil 58 | } 59 | 60 | func (r *IdClaims) GetSubject() (string, error) { 61 | return r.Issuer, nil 62 | } 63 | 64 | func (r *IdClaims) GetAudience() (jwt.ClaimStrings, error) { 65 | return jwt.ClaimStrings(r.Audience), nil 66 | } 67 | -------------------------------------------------------------------------------- /ziti/edge/posture/process_windows.go: -------------------------------------------------------------------------------- 1 | //go: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 | "os" 26 | "strings" 27 | 28 | "go.mozilla.org/pkcs7" 29 | ) 30 | 31 | func isProcessPath(expectedPath, processPath string) bool { 32 | return strings.Contains(strings.ToLower(expectedPath), strings.ToLower(processPath)) 33 | } 34 | 35 | func getSignerFingerprints(filePath string) ([]string, error) { 36 | peFile, err := pe.Open(filePath) 37 | 38 | if err != nil { 39 | return nil, err 40 | } 41 | defer peFile.Close() 42 | 43 | var virtualAddress uint32 44 | var size uint32 45 | switch t := peFile.OptionalHeader.(type) { 46 | case *pe.OptionalHeader32: 47 | virtualAddress = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress 48 | size = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].Size 49 | case *pe.OptionalHeader64: 50 | virtualAddress = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress 51 | size = t.DataDirectory[pe.IMAGE_DIRECTORY_ENTRY_SECURITY].Size 52 | } 53 | 54 | if virtualAddress <= 0 || size <= 0 { 55 | return nil, nil 56 | } 57 | 58 | binaryFile, err := os.Open(filePath) 59 | 60 | if err != nil { 61 | return nil, fmt.Errorf("failed to open process file: %v", err) 62 | } 63 | defer binaryFile.Close() 64 | 65 | pkcs7Signature := make([]byte, int64(size)) 66 | _, _ = binaryFile.ReadAt(pkcs7Signature, int64(virtualAddress+8)) 67 | 68 | pkcs7Obj, err := pkcs7.Parse(pkcs7Signature) 69 | 70 | if err != nil { 71 | return nil, fmt.Errorf("failed to parse pkcs7 block: %v", err) 72 | } 73 | var signerFingerprints []string 74 | for _, cert := range pkcs7Obj.Certificates { 75 | signerFingerprint := fmt.Sprintf("%x", sha1.Sum(cert.Raw)) 76 | signerFingerprints = append(signerFingerprints, signerFingerprint) 77 | } 78 | 79 | return signerFingerprints, nil 80 | } 81 | -------------------------------------------------------------------------------- /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/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. -------------------------------------------------------------------------------- /ziti/edge/network/seq_test.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "fmt" 5 | "github.com/openziti/channel/v4" 6 | "github.com/openziti/sdk-golang/ziti/edge" 7 | "github.com/stretchr/testify/require" 8 | "math" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | func Test_SeqNormalReadDeadline(t *testing.T) { 14 | closeNotify := make(chan struct{}) 15 | defer close(closeNotify) 16 | 17 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 18 | start := time.Now() 19 | readQ.SetReadDeadline(start.Add(10 * time.Millisecond)) 20 | 21 | req := require.New(t) 22 | _, err := readQ.GetNext() 23 | req.NotNil(err) 24 | req.ErrorIs(err, &ReadTimout{}) 25 | 26 | first := time.Now() 27 | req.True(first.Sub(start) >= 10*time.Millisecond) 28 | 29 | _, err = readQ.GetNext() 30 | req.NotNil(err) 31 | req.ErrorIs(err, &ReadTimout{}) 32 | req.True(time.Since(first) < time.Millisecond) 33 | } 34 | 35 | func Test_SeqNormalReadWithDeadline(t *testing.T) { 36 | closeNotify := make(chan struct{}) 37 | defer close(closeNotify) 38 | 39 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 40 | start := time.Now() 41 | readQ.SetReadDeadline(start.Add(10 * time.Millisecond)) 42 | 43 | req := require.New(t) 44 | 45 | data := make([]byte, 877) 46 | msg := edge.NewDataMsg(1, data) 47 | req.NoError(readQ.PutSequenced(msg)) 48 | 49 | val, err := readQ.GetNext() 50 | req.NoError(err) 51 | req.Equal(msg, val) 52 | } 53 | 54 | func Test_SeqNormalReadWithNoDeadline(t *testing.T) { 55 | closeNotify := make(chan struct{}) 56 | defer close(closeNotify) 57 | 58 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 59 | req := require.New(t) 60 | 61 | data := make([]byte, 877) 62 | msg := edge.NewDataMsg(1, data) 63 | req.NoError(readQ.PutSequenced(msg)) 64 | 65 | val, err := readQ.GetNext() 66 | req.NoError(err) 67 | req.Equal(msg, val) 68 | } 69 | 70 | func Test_SeqReadWithInterrupt(t *testing.T) { 71 | closeNotify := make(chan struct{}) 72 | defer close(closeNotify) 73 | 74 | readQ := NewNoopSequencer[*channel.Message](closeNotify, 4) 75 | start := time.Now() 76 | 77 | req := require.New(t) 78 | 79 | go func() { 80 | readQ.SetReadDeadline(start.Add(10 * time.Millisecond)) 81 | }() 82 | 83 | _, err := readQ.GetNext() 84 | req.NotNil(err) 85 | req.ErrorIs(err, &ReadTimout{}) 86 | first := time.Now() 87 | req.True(first.Sub(start) >= 10*time.Millisecond) 88 | 89 | _, err = readQ.GetNext() 90 | req.NotNil(err) 91 | req.ErrorIs(err, &ReadTimout{}) 92 | req.True(time.Since(first) < time.Millisecond) 93 | } 94 | 95 | func Test_GetMaxMsgMux(t *testing.T) { 96 | maxId := (math.MaxUint32 / 2) - 1 97 | fmt.Printf("max id: %d\n", maxId) 98 | } 99 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "os" 25 | "path/filepath" 26 | "strings" 27 | 28 | "github.com/michaelquigley/pfxlog" 29 | "github.com/mitchellh/go-ps" 30 | "github.com/shirou/gopsutil/v3/process" 31 | ) 32 | 33 | func NewProcessProvider() ProcessProvider { 34 | return &DefaultProcessProvider{} 35 | } 36 | 37 | type DefaultProcessProvider struct{} 38 | 39 | func (p *DefaultProcessProvider) GetProcessInfo(providedPath string) ProcessInfo { 40 | expectedPath := filepath.Clean(providedPath) 41 | 42 | processes, err := ps.Processes() 43 | 44 | if err != nil { 45 | pfxlog.Logger().Debugf("error getting Processes: %v", err) 46 | } 47 | 48 | if len(processes) == 0 { 49 | pfxlog.Logger().Warnf("total processes found was zero, this is unexpected") 50 | } 51 | 52 | for _, proc := range processes { 53 | if !isProcessPath(expectedPath, proc.Executable()) { 54 | continue 55 | } 56 | 57 | procDetails, err := process.NewProcess(int32(proc.Pid())) 58 | 59 | if err != nil { 60 | continue 61 | } 62 | 63 | executablePath, err := procDetails.Exe() 64 | 65 | if err != nil { 66 | continue 67 | } 68 | 69 | if strings.EqualFold(executablePath, expectedPath) { 70 | isRunning, _ := procDetails.IsRunning() 71 | file, err := os.ReadFile(executablePath) 72 | 73 | if err != nil { 74 | pfxlog.Logger().Warnf("could not read process executable file: %v", err) 75 | return ProcessInfo{ 76 | IsRunning: isRunning, 77 | Hash: "", 78 | SignerFingerprints: nil, 79 | } 80 | } 81 | 82 | sum := sha512.Sum512(file) 83 | hash := fmt.Sprintf("%x", sum[:]) 84 | 85 | signerFingerprints, err := getSignerFingerprints(executablePath) 86 | 87 | if err != nil { 88 | pfxlog.Logger().Warnf("could not read process signatures: %v", err) 89 | return ProcessInfo{ 90 | IsRunning: isRunning, 91 | Hash: hash, 92 | SignerFingerprints: nil, 93 | } 94 | } 95 | 96 | return ProcessInfo{ 97 | IsRunning: isRunning, 98 | Hash: hash, 99 | SignerFingerprints: signerFingerprints, 100 | } 101 | } 102 | } 103 | 104 | return ProcessInfo{ 105 | IsRunning: false, 106 | Hash: "", 107 | SignerFingerprints: nil, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /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 | externalCloseNotify: closeNotify, 29 | ch: make(chan T, channelDepth), 30 | deadlineNotify: make(chan struct{}), 31 | closeNotify: make(chan struct{}), 32 | } 33 | } 34 | 35 | type noopSeq[T any] struct { 36 | ch chan T 37 | externalCloseNotify <-chan struct{} 38 | deadlineNotify chan struct{} 39 | closeNotify chan struct{} 40 | closed atomic.Bool 41 | deadline concurrenz.AtomicValue[time.Time] 42 | readInProgress atomic.Bool 43 | } 44 | 45 | func (self *noopSeq[T]) Close() { 46 | if self.closed.CompareAndSwap(false, true) { 47 | close(self.closeNotify) 48 | } 49 | } 50 | 51 | func (seq *noopSeq[T]) PutSequenced(event T) error { 52 | select { 53 | case seq.ch <- event: 54 | return nil 55 | case <-seq.externalCloseNotify: 56 | return ErrClosed 57 | } 58 | } 59 | 60 | func (seq *noopSeq[T]) SetReadDeadline(deadline time.Time) { 61 | seq.deadline.Store(deadline) 62 | if seq.readInProgress.Load() { 63 | select { 64 | case seq.deadlineNotify <- struct{}{}: 65 | case <-time.After(5 * time.Millisecond): 66 | } 67 | } else { 68 | select { 69 | case seq.deadlineNotify <- struct{}{}: 70 | default: 71 | } 72 | } 73 | } 74 | 75 | func (seq *noopSeq[T]) GetNext() (T, error) { 76 | seq.readInProgress.Store(true) 77 | defer seq.readInProgress.Store(false) 78 | 79 | var val T 80 | 81 | for { 82 | deadline := seq.deadline.Load() 83 | 84 | var timeoutCh <-chan time.Time 85 | 86 | if !deadline.IsZero() { 87 | timeoutCh = time.After(time.Until(deadline)) 88 | } 89 | 90 | select { 91 | case val = <-seq.ch: 92 | return val, nil 93 | case <-seq.externalCloseNotify: 94 | // If we're closed, return any buffered values, otherwise return nil 95 | select { 96 | case val = <-seq.ch: 97 | return val, nil 98 | default: 99 | return val, ErrClosed 100 | } 101 | case <-seq.closeNotify: 102 | // If we're closed, return any buffered values, otherwise return nil 103 | select { 104 | case val = <-seq.ch: 105 | return val, nil 106 | default: 107 | return val, ErrClosed 108 | } 109 | case <-seq.deadlineNotify: 110 | continue 111 | case <-timeoutCh: 112 | // If we're timing out, return any buffered values, otherwise return nil 113 | select { 114 | case val = <-seq.ch: 115 | return val, nil 116 | default: 117 | return val, &ReadTimout{} 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | // 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 | // Deprecated: use NewSdkCollection() or NewSdkCollectionFromEnv() instead 24 | var DefaultCollection *CtxCollection 25 | 26 | // IdentitiesEnv is the string environment variable that is used to load identity files to populate DefaultCollection 27 | const IdentitiesEnv = "ZITI_IDENTITIES" 28 | 29 | func init() { 30 | DefaultCollection = NewSdkCollectionFromEnv(IdentitiesEnv, InterceptV1, ClientConfigV1) 31 | } 32 | 33 | // Deprecated: ForAllContexts iterates over all Context instances in the DefaultCollection and call the provided function `f`. 34 | // Usage of the DefaultCollection is advised against, and if this functionality is needed, implementations should 35 | // instantiate their own CtxCollection via NewSdkCollection() or NewSdkCollectionFromEnv() 36 | func ForAllContexts(f func(ctx Context) bool) { 37 | //Recreates sync.Map's Range() function which uses a bool return value to stop iterating (false == stop). 38 | //Done for backwards compatibility with ForAllContexts implementation 39 | keepGoing := true 40 | DefaultCollection.ForAll(func(c Context) { 41 | if !keepGoing { 42 | return 43 | } 44 | keepGoing = f(c) 45 | }) 46 | } 47 | 48 | // Deprecated: LoadContext loads a configuration from the supplied path into the DefaultCollection as a convenience. 49 | // Usage of the DefaultCollection is advised against, and if this functionality is needed, implementations should 50 | // instantiate their own CtxCollection via NewSdkCollection() or NewSdkCollectionFromEnv(). 51 | // 52 | // This function's behavior can be replicated with: 53 | // ``` 54 | // 55 | // collection = NewSdkCollection() 56 | // collection.ConfigTypes = []string{InterceptV1, ClientConfigV1} 57 | // collection.NewContextFromFile(configPath) 58 | // 59 | // ``` 60 | // 61 | // LoadContext will attempt to load a Config from the provided path, see NewConfigFromFile() for details. Additionally, 62 | // LoadContext will attempt to authenticate the Context. If it does not authenticate, it will not be added to the 63 | // DefaultCollection and an error will be returned. 64 | // ``` 65 | func LoadContext(configPath string) (Context, error) { 66 | ctx, err := DefaultCollection.NewContextFromFile(configPath) 67 | 68 | if err != nil { 69 | return nil, err 70 | } 71 | 72 | err = ctx.Authenticate() 73 | 74 | if err != nil { 75 | DefaultCollection.Remove(ctx) 76 | ctx.Close() 77 | } 78 | 79 | return ctx, nil 80 | } 81 | -------------------------------------------------------------------------------- /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/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://netfoundry.io/docs/openziti/learn/quickstarts/) 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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://netfoundry.io/docs/openziti/learn/quickstarts/) 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/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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 [quickstarts](https://netfoundry.io/docs/openziti/learn/quickstarts/) 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/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://netfoundry.io/docs/openziti/learn/quickstarts/) 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 | -------------------------------------------------------------------------------- /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" 25 | "sync/atomic" 26 | ) 27 | 28 | type LinkReceiveBuffer struct { 29 | sync.Mutex 30 | tree *btree.Tree 31 | sequence int32 32 | maxSequence int32 33 | size uint32 34 | txQueue chan *Payload 35 | } 36 | 37 | func NewLinkReceiveBuffer(txQueueSize int32) *LinkReceiveBuffer { 38 | return &LinkReceiveBuffer{ 39 | tree: btree.NewWith(10240, utils.Int32Comparator), 40 | sequence: -1, 41 | txQueue: make(chan *Payload, txQueueSize), 42 | } 43 | } 44 | 45 | func (buffer *LinkReceiveBuffer) Size() uint32 { 46 | return atomic.LoadUint32(&buffer.size) 47 | } 48 | 49 | func (buffer *LinkReceiveBuffer) ReceiveUnordered(x *Xgress, payload *Payload, maxSize uint32) bool { 50 | buffer.Lock() 51 | defer buffer.Unlock() 52 | 53 | if payload.GetSequence() <= buffer.sequence { 54 | x.dataPlane.GetMetrics().MarkDuplicatePayload() 55 | return true 56 | } 57 | 58 | if atomic.LoadUint32(&buffer.size) > maxSize && payload.Sequence > buffer.maxSequence { 59 | x.dataPlane.GetMetrics().MarkPayloadDropped() 60 | return false 61 | } 62 | 63 | treeSize := buffer.tree.Size() 64 | buffer.tree.Put(payload.GetSequence(), payload) 65 | if buffer.tree.Size() > treeSize { 66 | payloadSize := len(payload.Data) 67 | size := atomic.AddUint32(&buffer.size, uint32(payloadSize)) 68 | pfxlog.Logger().Tracef("Payload %v of size %v added to transmit buffer. New size: %v", payload.Sequence, payloadSize, size) 69 | if payload.Sequence > buffer.maxSequence { 70 | buffer.maxSequence = payload.Sequence 71 | } 72 | } else { 73 | x.dataPlane.GetMetrics().MarkDuplicatePayload() 74 | } 75 | 76 | buffer.queueNext() 77 | 78 | return true 79 | } 80 | 81 | func (buffer *LinkReceiveBuffer) queueNext() { 82 | if val := buffer.tree.LeftValue(); val != nil { 83 | payload := val.(*Payload) 84 | if payload.Sequence == buffer.sequence+1 { 85 | select { 86 | case buffer.txQueue <- payload: 87 | buffer.tree.Remove(payload.Sequence) 88 | buffer.sequence = payload.Sequence 89 | default: 90 | } 91 | } 92 | } 93 | } 94 | 95 | func (buffer *LinkReceiveBuffer) NextPayload(closeNotify <-chan struct{}) *Payload { 96 | select { 97 | case payload := <-buffer.txQueue: 98 | return payload 99 | default: 100 | } 101 | 102 | buffer.Lock() 103 | buffer.queueNext() 104 | buffer.Unlock() 105 | 106 | select { 107 | case payload := <-buffer.txQueue: 108 | return payload 109 | case <-closeNotify: 110 | } 111 | 112 | // closed, check if there's anything pending in the queue 113 | select { 114 | case payload := <-buffer.txQueue: 115 | return payload 116 | default: 117 | return nil 118 | } 119 | } 120 | 121 | func (buffer *LinkReceiveBuffer) Inspect() *RecvBufferDetail { 122 | buffer.Lock() 123 | defer buffer.Unlock() 124 | 125 | nextPayload := "none" 126 | if head := buffer.tree.LeftValue(); head != nil { 127 | payload := head.(*Payload) 128 | nextPayload = fmt.Sprintf("%v", payload.Sequence) 129 | } 130 | 131 | return &RecvBufferDetail{ 132 | Size: buffer.Size(), 133 | PayloadCount: uint32(buffer.tree.Size()), 134 | Sequence: buffer.sequence, 135 | MaxSequence: buffer.maxSequence, 136 | NextPayload: nextPayload, 137 | AcquiredSafely: true, 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /xgress/ordering_test.go: -------------------------------------------------------------------------------- 1 | package xgress 2 | 3 | import ( 4 | "context" 5 | "encoding/binary" 6 | "github.com/openziti/channel/v4" 7 | "github.com/stretchr/testify/require" 8 | "io" 9 | "sync/atomic" 10 | "testing" 11 | "time" 12 | ) 13 | 14 | type testConn struct { 15 | ch chan uint64 16 | closeNotify chan struct{} 17 | closed atomic.Bool 18 | } 19 | 20 | func (conn *testConn) Close() error { 21 | if conn.closed.CompareAndSwap(false, true) { 22 | close(conn.closeNotify) 23 | } 24 | return nil 25 | } 26 | 27 | func (conn *testConn) LogContext() string { 28 | return "test" 29 | } 30 | 31 | func (conn *testConn) ReadPayload() ([]byte, map[uint8][]byte, error) { 32 | <-conn.closeNotify 33 | return nil, nil, io.EOF 34 | } 35 | 36 | func (conn *testConn) WritePayload(bytes []byte, _ map[uint8][]byte) (int, error) { 37 | val := binary.LittleEndian.Uint64(bytes) 38 | conn.ch <- val 39 | return len(bytes), nil 40 | } 41 | 42 | func (conn *testConn) HandleControlMsg(ControlType, channel.Headers, ControlReceiver) error { 43 | return nil 44 | } 45 | 46 | type noopReceiveHandler struct { 47 | payloadIngester *PayloadIngester 48 | } 49 | 50 | func (n noopReceiveHandler) RetransmitPayload(srcAddr Address, payload *Payload) error { 51 | return nil 52 | } 53 | 54 | func (n noopReceiveHandler) GetMetrics() Metrics { 55 | return noopMetrics{} 56 | } 57 | 58 | func (n noopReceiveHandler) GetRetransmitter() *Retransmitter { 59 | return nil 60 | } 61 | 62 | func (n noopReceiveHandler) GetPayloadIngester() *PayloadIngester { 63 | return n.payloadIngester 64 | } 65 | 66 | func (n noopReceiveHandler) ForwardAcknowledgement(*Acknowledgement, Address) {} 67 | 68 | func (n noopReceiveHandler) ForwardPayload(*Payload, *Xgress, context.Context) {} 69 | 70 | func (n noopReceiveHandler) ForwardControlMessage(*Control, *Xgress) {} 71 | 72 | func Test_Ordering(t *testing.T) { 73 | closeNotify := make(chan struct{}) 74 | 75 | conn := &testConn{ 76 | ch: make(chan uint64, 1), 77 | closeNotify: make(chan struct{}), 78 | } 79 | 80 | x := NewXgress("test", "ctrl", "test", conn, Initiator, DefaultOptions(), nil) 81 | x.dataPlane = noopReceiveHandler{ 82 | payloadIngester: NewPayloadIngester(closeNotify), 83 | } 84 | go x.tx() 85 | 86 | defer x.Close() 87 | 88 | msgCount := 100000 89 | 90 | errorCh := make(chan error, 1) 91 | 92 | go func() { 93 | for i := 0; i < msgCount; i++ { 94 | data := make([]byte, 8) 95 | binary.LittleEndian.PutUint64(data, uint64(i)) 96 | payload := &Payload{ 97 | CircuitId: "test", 98 | Flags: SetOriginatorFlag(0, Terminator), 99 | RTT: 0, 100 | Sequence: int32(i), 101 | Headers: nil, 102 | Data: data, 103 | } 104 | if err := x.SendPayload(payload, 0, PayloadTypeXg); err != nil { 105 | errorCh <- err 106 | x.Close() 107 | return 108 | } 109 | } 110 | }() 111 | 112 | timeout := time.After(20 * time.Second) 113 | 114 | req := require.New(t) 115 | for i := 0; i < msgCount; i++ { 116 | select { 117 | case next := <-conn.ch: 118 | req.Equal(uint64(i), next) 119 | case <-conn.closeNotify: 120 | req.Fail("test failed with count at %v", i) 121 | case err := <-errorCh: 122 | req.NoError(err) 123 | case <-timeout: 124 | req.Failf("timed out", "count at %v", i) 125 | } 126 | } 127 | } 128 | 129 | type noopMetrics struct{} 130 | 131 | func (n noopMetrics) MarkAckReceived() {} 132 | 133 | func (n noopMetrics) MarkPayloadDropped() {} 134 | 135 | func (n noopMetrics) MarkDuplicateAck() {} 136 | 137 | func (n noopMetrics) MarkDuplicatePayload() {} 138 | 139 | func (n noopMetrics) BufferBlockedByLocalWindow() {} 140 | 141 | func (n noopMetrics) BufferUnblockedByLocalWindow() {} 142 | 143 | func (n noopMetrics) BufferBlockedByRemoteWindow() {} 144 | 145 | func (n noopMetrics) BufferUnblockedByRemoteWindow() {} 146 | 147 | func (n noopMetrics) PayloadWritten(time.Duration) {} 148 | 149 | func (n noopMetrics) BufferUnblocked(time.Duration) {} 150 | 151 | func (n noopMetrics) SendPayloadBuffered(int64) {} 152 | 153 | func (n noopMetrics) SendPayloadDelivered(int64) {} 154 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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-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/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/device-auth/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "net/url" 10 | "strings" 11 | "time" 12 | 13 | "github.com/dgrijalva/jwt-go" 14 | "github.com/openziti/edge-api/rest_model" 15 | "github.com/openziti/edge-api/rest_util" 16 | nfx509 "github.com/openziti/foundation/v2/x509" 17 | "github.com/openziti/sdk-golang/ziti" 18 | "gopkg.in/square/go-jose.v2/json" 19 | ) 20 | 21 | func die[T interface{}](res T, err error) T { 22 | if err != nil { 23 | log.Fatal(err) 24 | } 25 | return res 26 | } 27 | 28 | func main() { 29 | cfg := flag.String("config", "", "path to config file") 30 | openzitiURL := flag.String("ziti", "https://localhost:1280", "URL of the OpenZiti service") 31 | flag.Parse() 32 | 33 | var config *ziti.Config 34 | if cfg == nil || *cfg == "" { 35 | config = &ziti.Config{ 36 | ZtAPI: *openzitiURL, 37 | } 38 | // warning: this call is insecure and should not be used in production 39 | ca := die(rest_util.GetControllerWellKnownCas(*openzitiURL)) 40 | var buf bytes.Buffer 41 | _ = nfx509.MarshalToPem(ca, &buf) 42 | config.ID.CA = buf.String() 43 | } else { 44 | if openzitiURL == nil || *openzitiURL == "" { 45 | log.Fatal("OpenZiti URL must be specified") 46 | } 47 | config = die(ziti.NewConfigFromFile(*cfg)) 48 | } 49 | ztx := die(ziti.NewContext(config)) 50 | 51 | err := ztx.Authenticate() 52 | var provider *rest_model.ClientExternalJWTSignerDetail 53 | if err != nil { 54 | fmt.Println("Try authenticating with external provider") 55 | idps := die(ztx.GetExternalSigners()) 56 | for idx, idp := range idps { 57 | fmt.Printf("%d: %s\n", idx, *idp.Name) 58 | } 59 | 60 | fmt.Printf("Select provider allowing device code flow.\nEnter number[0-%d] to authenticate: ", len(idps)-1) 61 | var id int 62 | _ = die(fmt.Scanf("%d", &id)) 63 | 64 | provider = idps[id] 65 | } 66 | if provider == nil { 67 | log.Fatal("No provider found") 68 | } 69 | fmt.Printf("Using %s\n", *provider.Name) 70 | 71 | resp := die(http.Get(*provider.ExternalAuthURL + "/.well-known/openid-configuration")) 72 | var oidcConfig map[string]interface{} 73 | _ = json.NewDecoder(resp.Body).Decode(&oidcConfig) 74 | 75 | deviceAuth := oidcConfig["device_authorization_endpoint"].(string) 76 | scopes := append(provider.Scopes, "openid") 77 | ss := strings.Join(scopes, " ") 78 | resp = die(http.PostForm(deviceAuth, url.Values{ 79 | "client_id": {*provider.ClientID}, 80 | "scope": {ss}, 81 | "audience": {*provider.Audience}, 82 | })) 83 | 84 | var deviceCode map[string]interface{} 85 | _ = json.NewDecoder(resp.Body).Decode(&deviceCode) 86 | if completeUrl, ok := deviceCode["verification_uri_complete"]; ok { 87 | fmt.Printf("Open %s in your browser\n", completeUrl.(string)) 88 | } else if verifyUrl, ok := deviceCode["verification_uri"]; ok { 89 | fmt.Printf("Open %s in your browser, and use code %s\n", 90 | verifyUrl.(string), deviceCode["user_code"].(string)) 91 | } else { 92 | log.Fatal("Unable to determine verification URL") 93 | } 94 | 95 | interval := time.Duration(int(deviceCode["interval"].(float64))) * time.Second 96 | 97 | var token map[string]interface{} 98 | for { 99 | clear(token) 100 | time.Sleep(interval) 101 | 102 | tokenUrl := oidcConfig["token_endpoint"].(string) 103 | resp = die(http.PostForm(tokenUrl, url.Values{ 104 | "client_id": {*provider.ClientID}, 105 | "device_code": {deviceCode["device_code"].(string)}, 106 | "grant_type": {"urn:ietf:params:oauth:grant-type:device_code"}, 107 | })) 108 | 109 | json.NewDecoder(resp.Body).Decode(&token) 110 | errmsg, hasErr := token["error"] 111 | if !hasErr { 112 | break 113 | } 114 | errormsg := errmsg.(string) 115 | if errormsg == "authorization_pending" { 116 | fmt.Println("Waiting for user to authorize...") 117 | continue 118 | } 119 | log.Fatal(errormsg) 120 | } 121 | 122 | accessToken := token["access_token"].(string) 123 | tok, _ := jwt.Parse(accessToken, nil) 124 | if claims, ok := tok.Claims.(jwt.MapClaims); ok { 125 | for k, v := range claims { 126 | fmt.Printf("\t%s: %v\n", k, v) 127 | } 128 | } 129 | ztx.LoginWithJWT(accessToken) 130 | 131 | err = ztx.Authenticate() 132 | if err != nil { 133 | log.Fatal(err) 134 | } 135 | fmt.Println("Authenticated") 136 | 137 | services, _ := ztx.GetServices() 138 | fmt.Println("Available Services:") 139 | for _, svc := range services { 140 | fmt.Printf("\t%s\n", *svc.Name) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /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 | LastSizeSent uint32 `json:"lastSizeSent"` 66 | } 67 | 68 | type SendBufferDetail struct { 69 | WindowSize uint32 `json:"windowSize"` 70 | QueuedPayloadCount int `json:"queuedPayloadCount"` 71 | LinkSendBufferSize uint32 `json:"linkSendBufferSize"` 72 | LinkRecvBufferSize uint32 `json:"linkRecvBufferSize"` 73 | Accumulator uint32 `json:"accumulator"` 74 | SuccessfulAcks uint32 `json:"successfulAcks"` 75 | DuplicateAcks uint32 `json:"duplicateAcks"` 76 | Retransmits uint32 `json:"retransmits"` 77 | Closed bool `json:"closed"` 78 | BlockedByLocalWindow bool `json:"blockedByLocalWindow"` 79 | BlockedByRemoteWindow bool `json:"blockedByRemoteWindow"` 80 | RetxScale float64 `json:"retxScale"` 81 | RetxThreshold uint32 `json:"retxThreshold"` 82 | TimeSinceLastRetx string `json:"timeSinceLastRetx"` 83 | CloseWhenEmpty bool `json:"closeWhenEmpty"` 84 | AcquiredSafely bool `json:"acquiredSafely"` 85 | } 86 | 87 | type RecvBufferDetail struct { 88 | Size uint32 `json:"size"` 89 | PayloadCount uint32 `json:"payloadCount"` 90 | Sequence int32 `json:"sequence"` 91 | MaxSequence int32 `json:"maxSequence"` 92 | NextPayload string `json:"nextPayload"` 93 | AcquiredSafely bool `json:"acquiredSafely"` 94 | } 95 | 96 | type CircuitsDetail struct { 97 | Circuits map[string]*CircuitDetail `json:"circuits"` 98 | } 99 | 100 | type CircuitDetail struct { 101 | CircuitId string `json:"circuitId"` 102 | ConnId uint32 `json:"connId"` 103 | Address string `json:"address"` 104 | Originator string `json:"originator"` 105 | IsXgress bool `json:"isXgress"` 106 | CtrlId string `json:"ctrlId"` 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/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/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 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/openziti/sdk-golang 2 | 3 | go 1.24.7 4 | 5 | require ( 6 | github.com/Jeffail/gabs v1.4.0 7 | github.com/cenkalti/backoff/v4 v4.3.0 8 | github.com/emirpasic/gods v1.18.1 9 | github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa 10 | github.com/go-openapi/runtime v0.29.0 11 | github.com/go-openapi/strfmt v0.24.0 12 | github.com/go-resty/resty/v2 v2.16.5 13 | github.com/golang-jwt/jwt/v5 v5.3.0 14 | github.com/google/uuid v1.6.0 15 | github.com/kataras/go-events v0.0.3 16 | github.com/michaelquigley/pfxlog v0.6.10 17 | github.com/mitchellh/go-ps v1.0.0 18 | github.com/mitchellh/mapstructure v1.5.0 19 | github.com/openziti/channel/v4 v4.2.41 20 | github.com/openziti/edge-api v0.26.51 21 | github.com/openziti/foundation/v2 v2.0.79 22 | github.com/openziti/identity v1.0.118 23 | github.com/openziti/metrics v1.4.2 24 | github.com/openziti/secretstream v0.1.42 25 | github.com/openziti/transport/v2 v2.0.198 26 | github.com/orcaman/concurrent-map/v2 v2.0.1 27 | github.com/pkg/errors v0.9.1 28 | github.com/rcrowley/go-metrics v0.0.0-20250401214520-65e299d6c5c9 29 | github.com/shirou/gopsutil/v3 v3.24.5 30 | github.com/sirupsen/logrus v1.9.3 31 | github.com/stretchr/testify v1.11.1 32 | github.com/zitadel/oidc/v3 v3.45.0 33 | go.mozilla.org/pkcs7 v0.9.0 34 | golang.org/x/oauth2 v0.31.0 35 | golang.org/x/sys v0.37.0 36 | google.golang.org/protobuf v1.36.10 37 | ) 38 | 39 | require ( 40 | github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect 41 | github.com/davecgh/go-spew v1.1.1 // indirect 42 | github.com/fsnotify/fsnotify v1.9.0 // indirect 43 | github.com/go-jose/go-jose/v4 v4.0.5 // indirect 44 | github.com/go-logr/logr v1.4.3 // indirect 45 | github.com/go-logr/stdr v1.2.2 // indirect 46 | github.com/go-ole/go-ole v1.2.6 // indirect 47 | github.com/go-openapi/analysis v0.24.0 // indirect 48 | github.com/go-openapi/errors v0.22.3 // indirect 49 | github.com/go-openapi/jsonpointer v0.22.1 // indirect 50 | github.com/go-openapi/jsonreference v0.21.2 // indirect 51 | github.com/go-openapi/loads v0.23.1 // indirect 52 | github.com/go-openapi/spec v0.22.0 // indirect 53 | github.com/go-openapi/swag v0.25.1 // indirect 54 | github.com/go-openapi/swag/cmdutils v0.25.1 // indirect 55 | github.com/go-openapi/swag/conv v0.25.1 // indirect 56 | github.com/go-openapi/swag/fileutils v0.25.1 // indirect 57 | github.com/go-openapi/swag/jsonname v0.25.1 // indirect 58 | github.com/go-openapi/swag/jsonutils v0.25.1 // indirect 59 | github.com/go-openapi/swag/loading v0.25.1 // indirect 60 | github.com/go-openapi/swag/mangling v0.25.1 // indirect 61 | github.com/go-openapi/swag/netutils v0.25.1 // indirect 62 | github.com/go-openapi/swag/stringutils v0.25.1 // indirect 63 | github.com/go-openapi/swag/typeutils v0.25.1 // indirect 64 | github.com/go-openapi/swag/yamlutils v0.25.1 // indirect 65 | github.com/go-openapi/validate v0.25.0 // indirect 66 | github.com/go-viper/mapstructure/v2 v2.4.0 // indirect 67 | github.com/gorilla/mux v1.8.1 // indirect 68 | github.com/gorilla/securecookie v1.1.2 // indirect 69 | github.com/gorilla/websocket v1.5.3 // indirect 70 | github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect 71 | github.com/mattn/go-colorable v0.1.14 // indirect 72 | github.com/mattn/go-isatty v0.0.20 // indirect 73 | github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect 74 | github.com/miekg/pkcs11 v1.1.1 // indirect 75 | github.com/muhlemmer/gu v0.3.1 // indirect 76 | github.com/oklog/ulid v1.3.1 // indirect 77 | github.com/parallaxsecond/parsec-client-go v0.0.0-20221025095442-f0a77d263cf9 // indirect 78 | github.com/pmezard/go-difflib v1.0.0 // indirect 79 | github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect 80 | github.com/shoenig/go-m1cpu v0.1.6 // indirect 81 | github.com/speps/go-hashids v2.0.0+incompatible // indirect 82 | github.com/tklauser/go-sysconf v0.3.12 // indirect 83 | github.com/tklauser/numcpus v0.6.1 // indirect 84 | github.com/yusufpapurcu/wmi v1.2.4 // indirect 85 | github.com/zitadel/logging v0.6.2 // indirect 86 | github.com/zitadel/schema v1.3.1 // indirect 87 | go.mongodb.org/mongo-driver v1.17.4 // indirect 88 | go.opentelemetry.io/auto/sdk v1.2.1 // indirect 89 | go.opentelemetry.io/otel v1.38.0 // indirect 90 | go.opentelemetry.io/otel/metric v1.38.0 // indirect 91 | go.opentelemetry.io/otel/trace v1.38.0 // indirect 92 | go.yaml.in/yaml/v3 v3.0.4 // indirect 93 | golang.org/x/crypto v0.43.0 // indirect 94 | golang.org/x/net v0.45.0 // indirect 95 | golang.org/x/sync v0.17.0 // indirect 96 | golang.org/x/term v0.36.0 // indirect 97 | golang.org/x/text v0.30.0 // indirect 98 | gopkg.in/yaml.v3 v3.0.1 // indirect 99 | nhooyr.io/websocket v1.8.17 // indirect 100 | ) 101 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ziti/edge/network/xg_adapter.go: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/michaelquigley/pfxlog" 10 | "github.com/openziti/channel/v4" 11 | "github.com/openziti/sdk-golang/edgexg" 12 | "github.com/openziti/sdk-golang/xgress" 13 | "github.com/openziti/sdk-golang/ziti/edge" 14 | "github.com/sirupsen/logrus" 15 | ) 16 | 17 | type XgAdapter struct { 18 | conn *edgeConn 19 | readC chan []byte 20 | env xgress.Env 21 | xg *xgress.Xgress 22 | writeAdapter *xgress.WriteAdapter 23 | } 24 | 25 | func (self *XgAdapter) HandleXgressClose(x *xgress.Xgress) { 26 | xgCloseMsg := channel.NewMessage(edge.ContentTypeXgClose, []byte(self.xg.CircuitId())) 27 | if err := xgCloseMsg.WithTimeout(5 * time.Second).Send(self.conn.GetControlSender()); err != nil { 28 | pfxlog.Logger().WithError(err).Error("failed to send close xg close message") 29 | } 30 | 31 | // see note in close 32 | self.conn.msgMux.Remove(self.conn) 33 | } 34 | 35 | func (self *XgAdapter) ForwardPayload(payload *xgress.Payload, _ *xgress.Xgress, ctx context.Context) { 36 | msg := payload.Marshall() 37 | msg.PutUint32Header(edge.ConnIdHeader, self.conn.Id()) 38 | 39 | if err := msg.WithContext(ctx).SendAndWaitForWire(self.conn.GetDefaultSender()); err != nil { 40 | pfxlog.Logger().WithField("circuitId", payload.CircuitId).WithError(err).Error("failed to send payload") 41 | } 42 | } 43 | 44 | func (self *XgAdapter) RetransmitPayload(srcAddr xgress.Address, payload *xgress.Payload) error { 45 | msg := payload.Marshall() 46 | sent, err := self.conn.MsgChannel.GetDefaultSender().TrySend(msg) 47 | if err != nil { 48 | // if the channel is closed, close the xgress 49 | if self.conn.MsgChannel.GetChannel().IsClosed() { 50 | self.xg.Close() 51 | } 52 | return err 53 | } 54 | 55 | if !sent { 56 | pfxlog.Logger().WithField("circuitId", payload.CircuitId).WithError(err).Debug("payload dropped") 57 | } 58 | 59 | return nil 60 | } 61 | 62 | func (self *XgAdapter) ForwardControlMessage(control *xgress.Control, x *xgress.Xgress) { 63 | msg := control.Marshall() 64 | if err := self.conn.MsgChannel.GetDefaultSender().Send(msg); err != nil { 65 | pfxlog.Logger().WithError(err).Error("failed to forward control message") 66 | } 67 | } 68 | 69 | func (self *XgAdapter) ForwardAcknowledgement(ack *xgress.Acknowledgement, address xgress.Address) { 70 | msg := ack.Marshall() 71 | if err := self.conn.MsgChannel.GetDefaultSender().Send(msg); err != nil { 72 | pfxlog.Logger().WithError(err).Error("failed to send acknowledgement") 73 | } 74 | } 75 | 76 | func (self *XgAdapter) GetRetransmitter() *xgress.Retransmitter { 77 | return self.env.GetRetransmitter() 78 | } 79 | 80 | func (self *XgAdapter) GetPayloadIngester() *xgress.PayloadIngester { 81 | return self.env.GetPayloadIngester() 82 | } 83 | 84 | func (self *XgAdapter) GetMetrics() xgress.Metrics { 85 | return self.env.GetMetrics() 86 | } 87 | 88 | func (self *XgAdapter) Close() error { 89 | return nil 90 | } 91 | 92 | func (self *XgAdapter) LogContext() string { 93 | return fmt.Sprintf("xg/%s", self.conn.GetCircuitId()) 94 | } 95 | 96 | func (self *XgAdapter) ReadPayload() ([]byte, map[uint8][]byte, error) { 97 | return nil, nil, errors.New("should never be called") 98 | } 99 | 100 | func (self *XgAdapter) WritePayload(bytes []byte, headers map[uint8][]byte) (int, error) { 101 | var msgUUID []byte 102 | var edgeHdrs map[int32][]byte 103 | 104 | if headers != nil { 105 | msgUUID = headers[xgress.HeaderKeyUUID] 106 | 107 | edgeHdrs = make(map[int32][]byte) 108 | for k, v := range headers { 109 | if edgeHeader, found := edgexg.HeadersFromFabric[k]; found { 110 | edgeHdrs[edgeHeader] = v 111 | } 112 | } 113 | } 114 | 115 | msg := edge.NewDataMsg(self.conn.Id(), bytes) 116 | if msgUUID != nil { 117 | msg.Headers[edge.UUIDHeader] = msgUUID 118 | } 119 | 120 | for k, v := range edgeHdrs { 121 | msg.Headers[k] = v 122 | } 123 | 124 | if err := self.conn.readQ.PutSequenced(msg); err != nil { 125 | logrus.WithFields(edge.GetLoggerFields(msg)).WithError(err). 126 | WithField("circuitId", self.conn.circuitId). 127 | Error("error pushing edge message to sequencer") 128 | return 0, err 129 | } 130 | 131 | logrus.WithFields(edge.GetLoggerFields(msg)).Debugf("received %v bytes (msg type: %v)", len(msg.Body), msg.ContentType) 132 | return len(msg.Body), nil 133 | } 134 | 135 | func (self *XgAdapter) FlowFromFabricToXgressClosed() { 136 | pfxlog.Logger().WithField("circuitId", self.conn.circuitId). 137 | Debug("fabric to sdk flow complete") 138 | self.conn.readQ.Close() 139 | } 140 | 141 | func (self *XgAdapter) HandleControlMsg(controlType xgress.ControlType, headers channel.Headers, responder xgress.ControlReceiver) error { 142 | //TODO implement me 143 | panic("implement me") 144 | } 145 | --------------------------------------------------------------------------------