From 3653359ca88906f21a217c7a7c9a861716a59283 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 17 Jan 2025 10:39:05 +0100 Subject: [PATCH 1/2] remove publish ports section --- README.md | 2 +- border0_api/border0.go | 525 ----------------------------- border0_api/border0_test.go | 329 ------------------ border0_api/structs.go | 149 -------- clab/config.go | 1 - clab/register.go | 2 - cmd/border0.go | 58 ---- docs/cmd/tools/border0/login.md | 31 -- docs/cmd/tools/mysocketio/login.md | 31 -- docs/index.md | 2 +- docs/manual/kinds/linux.md | 3 +- docs/manual/nodes.md | 18 - docs/manual/published-ports.md | 141 -------- docs/rn/0.12.0.md | 15 +- docs/rn/0.19.md | 2 +- go.mod | 5 - go.sum | 12 - mkdocs.yml | 1 - nodes/border0/border0.go | 121 ------- types/node_definition.go | 9 - types/topology.go | 15 - types/types.go | 3 - 22 files changed, 17 insertions(+), 1458 deletions(-) delete mode 100644 border0_api/border0.go delete mode 100644 border0_api/border0_test.go delete mode 100644 border0_api/structs.go delete mode 100644 cmd/border0.go delete mode 100644 docs/cmd/tools/border0/login.md delete mode 100644 docs/cmd/tools/mysocketio/login.md delete mode 100644 docs/manual/published-ports.md delete mode 100644 nodes/border0/border0.go diff --git a/README.md b/README.md index fb69efd5b..1ebb6ac17 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ This short clip briefly demonstrates containerlab features and explains its purp * **Labs and demos** Containerlab was meant to be a tool for provisioning networking labs built with containers. It is free, open and ubiquitous. No software apart from Docker is required! As with any lab environment it allows the users to validate features, topologies, perform interop testing, datapath testing, etc. - It is also a perfect companion for your next demo. Deploy the lab fast, with all its configuration stored as a code -> destroy when done. Easily and [securely share lab access](https://containerlab.dev/manual/published-ports) if needed. + It is also a perfect companion for your next demo. Deploy the lab fast, with all its configuration stored as a code -> destroy when done. * **Testing and CI** Because of the containerlab's single-binary packaging and code-based lab definition files, it was never that easy to spin up a test bed for CI. Gitlab CI, Github Actions and virtually any CI system will be able to spin up containerlab topologies in a single simple command. * **Telemetry validation** diff --git a/border0_api/border0.go b/border0_api/border0.go deleted file mode 100644 index e9a9ea79e..000000000 --- a/border0_api/border0.go +++ /dev/null @@ -1,525 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package border0_api - -import ( - "bytes" - "context" - "encoding/json" - "fmt" - "io" - "net/http" - "net/url" - "os" - "os/exec" - "path/filepath" - "runtime" - "strconv" - "strings" - "time" - - "github.com/cenkalti/backoff" - "github.com/golang-jwt/jwt" - log "github.com/sirupsen/logrus" - "github.com/skratchdot/open-golang/open" - "github.com/srl-labs/containerlab/nodes" - "github.com/srl-labs/containerlab/utils" - "gopkg.in/yaml.v2" -) - -const ( - apiUrl = "https://api.border0.com/api/v1" - portalUrl = "https://portal.border0.com" - ENV_NAME_BORDER0_ADMIN_TOKEN = "BORDER0_ADMIN_TOKEN" - ENV_NAME_BORDER0_API = "BORDER0_API" - ENV_NAME_BORDER0_PORTAL = "BORDER0_PORTAL" -) - -var supportedSockTypes = []string{"ssh", "tls", "http", "https"} - -// to avoid multiple token lookups etc. we'll cache the token. -var tokenCache = "" - -type deviceAuthorization struct { - Token string `json:"token,omitempty"` -} - -type deviceAuthorizationStatus struct { - Token string `json:"token,omitempty"` - State string `json:"state,omitempty"` -} - -func createDeviceAuthorization(ctx context.Context) (string, error) { - deviceAuthResp := &deviceAuthorization{} - err := Request(ctx, http.MethodPost, "device_authorizations", deviceAuthResp, nil, false, "") - if err != nil { - return "", err - } - - return deviceAuthResp.Token, nil -} - -func getDeviceAuthorizationStatus(ctx context.Context, deviceAuthToken string) (*deviceAuthorizationStatus, error) { - deviceAuthStatusResp := &deviceAuthorizationStatus{} - - err := Request(ctx, http.MethodGet, "device_authorizations", deviceAuthStatusResp, nil, false, deviceAuthToken) - if err != nil { - return nil, fmt.Errorf("failed to retrieve Border0 device authorization status: %v", err) - } - - return deviceAuthStatusResp, nil -} - -func handleDeviceAuthorization(ctx context.Context, deviceAuthToken string, disableBrowser bool) (string, error) { - deviceAuthJWT, _ := jwt.Parse(deviceAuthToken, nil) - if deviceAuthJWT == nil { - return "", fmt.Errorf("failed to decode Border0 device authorization token") - } - claims := deviceAuthJWT.Claims.(jwt.MapClaims) - deviceIdentifier := fmt.Sprint(claims["identifier"]) - - // Try opening the system's browser automatically. The error is ignored because the desired behavior of the - // handler is the same regardless of whether opening the browser fails or succeeds -- we still print the URL. - // This is desirable because in the event opening the browser succeeds, the customer may still accidentally - // close the new tab / browser session, or may want to authenticate in a different browser / session. In the - // event that opening the browser fails, the customer may still complete authenticating by navigating to the - // URL in a different device. - - url := fmt.Sprintf("%s/login?device_identifier=%v", getPortalUrl(), url.QueryEscape(deviceIdentifier)) - - fmt.Printf("Please navigate to the URL below in order to complete the login process:\n%s\n", url) - - // check if the disableBrowser flag is set - if !disableBrowser { - // check if we're on DARWIN and if we're running as sudo, if so, make sure we open the browser as the user - // this prevents folks from not having access to credentials , sessions, etc - sudoUsername := os.Getenv("SUDO_USER") - sudoAttempt := false - if runtime.GOOS == "darwin" && sudoUsername != "" { - err := exec.Command("sudo", "-u", sudoUsername, "open", url).Run() - if err == nil { - // If for some reason this failed, we'll try again to standard way - sudoAttempt = true - } - } - if !sudoAttempt { - _ = open.Run(url) - } - } - - exponentialBackoff := backoff.NewExponentialBackOff() - exponentialBackoff.InitialInterval = 1 * time.Second - exponentialBackoff.MaxInterval = 5 * time.Second - exponentialBackoff.Multiplier = 1.3 - exponentialBackoff.MaxElapsedTime = 3 * time.Minute - - var token *deviceAuthorizationStatus - - retryFn := func() error { - var err error - token, err = getDeviceAuthorizationStatus(ctx, deviceAuthToken) - if err != nil { - return err - } - if token.Token == "" || token.State == "not_authorized" { - return fmt.Errorf("device authorization code is not authorized") - } - return nil - } - - err := backoff.Retry(retryFn, exponentialBackoff) - if err != nil { - return "", fmt.Errorf("failed to log you in, make sure that you have authenticated using the link above: %v", err) - } - - fmt.Println("Login successful!") - - return token.Token, nil -} - -// Login performs a login to border0.com and stores the retrieved the access-token in the cwd. -func Login(ctx context.Context, email, password string, disableBrowser bool) error { - var token string - - // if email is not set, we default to Border0's OAuth2 Device Authorization Flow. - if email == "" { - deviceAuthToken, err := createDeviceAuthorization(ctx) - if err != nil { - return fmt.Errorf("failed to initiate Border0 device authorization flow: %v", err) - } - - token, err = handleDeviceAuthorization(ctx, deviceAuthToken, disableBrowser) - if err != nil { - return fmt.Errorf("failed to authenticate you against Border0: %v", err) - } - } else { - // if password not set read from terminal - if password == "" { - var err error - password, err = utils.ReadPasswordFromTerminal() - if err != nil { - return err - } - } - // prepare a LoginRequest - loginReq := &LoginRequest{ - Email: email, - Password: password, - } - // init a LoginResponse - loginResp := &LoginResponse{} - - // execute the request - err := Request(ctx, http.MethodPost, "login", loginResp, loginReq, false, "") - if err != nil { - return err - } - - token = loginResp.Token - } - - if err := writeToken(token); err != nil { - return err - } - return nil -} - -func getApiUrl() string { - if os.Getenv(ENV_NAME_BORDER0_API) != "" { - return os.Getenv(ENV_NAME_BORDER0_API) - } else { - return apiUrl - } -} - -func getPortalUrl() string { - if os.Getenv(ENV_NAME_BORDER0_PORTAL) != "" { - return os.Getenv(ENV_NAME_BORDER0_PORTAL) - } else { - return portalUrl - } -} - -// getToken retrieved the border0 access-token as a string. -func getToken() (string, error) { - // return the cached token - if tokenCache != "" { - return tokenCache, nil - } - tokenData := "" - // Environement variable provided token - if os.Getenv(ENV_NAME_BORDER0_ADMIN_TOKEN) != "" { - tokenData = os.Getenv(ENV_NAME_BORDER0_ADMIN_TOKEN) - log.Debugf("sourcing Border0.com token from %q environment variable", tokenData) - } else { - // resolve the token file - tokenFile, err := tokenfile() - if err != nil { - return "", err - } - // read in the token from the resolved file - tokenBytes, err := os.ReadFile(tokenFile) - if err != nil { - return "", err - } - tokenData = string(tokenBytes) - } - - // also store the token is cache - tokenCache := strings.TrimSpace(tokenData) - - return tokenCache, nil -} - -// checkPoliciesExist given a Map of policy names, will figure out if these policies already on the border0 side. -func checkPoliciesExist(ctx context.Context, policyNames map[string]struct{}) error { - // retrieve the existing policies - policies, err := GetExistingPolicies(ctx) - if err != nil { - return err - } - // keep track of the non-found policy names - notFound := []string{} -OUTER: - // iterate over the given policy names - for name := range policyNames { - // iterate over the retrieved policies - for _, policy := range policies { - if name == policy.Name { - // if a policy with the name is found continue with next - continue OUTER - } - } - // if not found add to the list - notFound = append(notFound, name) - } - // if we have non found poolicies, raise error - if len(notFound) > 0 { - return fmt.Errorf("border0.com policies %q referenced but they don't exist", strings.Join(notFound, ", ")) - } - // if we've found all items return nil - return nil -} - -// tokenfile retrieve the location of the token file. It can reside in different locations and the most valid will be resolved here -// already validates that the file exists. -func tokenfile() (string, error) { - tokenFile := "" - // iterate over the possible border0.com token file locations - for i := 0; i <= 1; i++ { - switch i { - case 0: - // Current Working Directory location - cwd, _ := os.Getwd() - tokenFile = fmt.Sprintf("%s/.border0_token", cwd) - case 1: - // Home directory location - tokenFile = fmt.Sprintf("%s/.border0/token", os.Getenv("HOME")) - } - // if file exists return - if utils.FileExists(tokenFile) { - return tokenFile, nil - } - } - // no valid file found, return error - return "", fmt.Errorf("no access-token found, please login to border0.com first e.g use `containerlab tools border0 login`") -} - -// cwdTokenFilePath get the abspath of the token file in the current working directory. -func cwdTokenFilePath() string { - cwd, err := os.Getwd() - if err != nil { - return "" - } - return filepath.Join(cwd, ".border0_token") -} - -// GetExistingPolicies retrieved the existing policies from border0.com. -func GetExistingPolicies(ctx context.Context) ([]Policy, error) { - var policies []Policy - err := Request(ctx, http.MethodGet, "policies", &policies, nil, true, "") - if err != nil { - return nil, err - } - return policies, nil -} - -// Request is the helper function that handels the http requests, as well as the marshalling of request structs and unmarshalling of responses. -func Request(ctx context.Context, method string, url string, targetStruct interface{}, - data interface{}, requireAccessToken bool, token string, -) error { - jv, _ := json.Marshal(data) - body := bytes.NewBuffer(jv) - - req, _ := http.NewRequestWithContext(ctx, method, fmt.Sprintf("%s/%s", getApiUrl(), url), body) - - if token != "" { - req.Header.Add("x-access-token", strings.TrimSpace(token)) - } - - // try to find the token in the environment - if requireAccessToken { - var err error - token, err = getToken() - if err != nil { - return err - } - } - if token != "" { - token = strings.TrimSpace(token) - req.Header.Add("x-access-token", token) - } - req.Header.Add("x-client-requested-with", "containerlab") - req.Header.Set("Content-Type", "application/json") - - client := &http.Client{} - resp, err := client.Do(req) - if err != nil { - return err - } - - defer resp.Body.Close() - - if resp.StatusCode == 401 { - return fmt.Errorf("401 Unauthorized, maybe the token expired") - } - - if resp.StatusCode == 429 { - responseData, _ := io.ReadAll(resp.Body) - return fmt.Errorf("rate limit error: %v", string(responseData)) - } - - if resp.StatusCode == 404 { - return fmt.Errorf("404 NotFound") - } - - if resp.StatusCode < 200 || resp.StatusCode > 204 { - var errorMessage ErrorMessage - json.NewDecoder(resp.Body).Decode(&errorMessage) - - return fmt.Errorf("failed to create object (%d) %v", resp.StatusCode, errorMessage.ErrorMessage) - } - - if resp.StatusCode == 204 { - return nil - } - - err = json.NewDecoder(resp.Body).Decode(targetStruct) - if err != nil { - return fmt.Errorf("error decoding body: %w", err) - } - - return nil -} - -// RefreshLogin checking the validity of the login token as well as it. -func RefreshLogin(ctx context.Context) error { - t := &LoginRefreshResponse{} - log.Debug("Validating and refreshing border0.com token") - err := Request(ctx, http.MethodPost, "login/refresh", t, nil, true, "") - if err != nil { - return err - } - err = writeToken(t.Token) - if err != nil { - return err - } - return nil -} - -// writeToken writes the given token to a file. -func writeToken(token string) error { - absPathToken := cwdTokenFilePath() - - err := os.WriteFile(absPathToken, []byte(token), 0600) - if err != nil { - return fmt.Errorf("failed to write border0.com token file as %s: %v", - absPathToken, err) - } - log.Debugf("Saved border0.com token to %s", absPathToken) - // also update the token cache - tokenCache = token - return nil -} - -// CreateBorder0Config inspects the `publish` section of the nodes configuration and builds a configuration for -// the border0.com cli clients "Static Sockets plugin" [https://docs.border0.com/docs/static-sockets-plugin] -func CreateBorder0Config(ctx context.Context, nodesMap map[string]nodes.Node, labname string) (string, error) { - log.Debug("Creating the border0.com configuration") - // acquire token - border0Token, err := getToken() - if err != nil { - return "", err - } - // init config struct - yamlConfig := &StaticSocketsConfig{ - Connector: &configConnector{ - Name: labname, - }, - Credentials: &configCredentials{ - Token: border0Token, - }, - Sockets: []map[string]*configSocket{}, - } - - // helper map to collect all the referenced policies for later existence check - policyNames := map[string]struct{}{} - - // iterate over structs to generate socket configs - for _, n := range nodesMap { - for _, socket := range n.Config().Publish { - // Parse the socket config - sockConfig, err := ParseSocketCfg(socket, n.Config().LongName) - if err != nil { - return "", err - } - - // add the referenced policies to the map for later checking - for _, policy := range sockConfig.Policies { - policyNames[policy] = struct{}{} - } - - // determine the socketname - socketName := fmt.Sprintf("%s-%s-%d", n.Config().LongName, sockConfig.Type, sockConfig.Port) - yamlConfig.Sockets = append(yamlConfig.Sockets, map[string]*configSocket{ - socketName: sockConfig, - }) - } - } - - // check for the existence of referenced policies - err = checkPoliciesExist(ctx, policyNames) - if err != nil { - return "", err - } - - // marshall the config into a []byte - bconfig, err := yaml.Marshal(yamlConfig) - if err != nil { - return "", err - } - - // return yaml based string configuration - return string(bconfig), nil -} - -// ParseSocketCfg parses the nodes publish configuration string and returns resulting *configSocket. -func ParseSocketCfg(s, host string) (*configSocket, error) { - result := &configSocket{} - // split the socket definition string - split := strings.Split(s, "/") - if len(split) > 3 { - return result, fmt.Errorf("wrong publish section %q. should be /[/,] i.e. tls/22, tls/22/mypolicy1 or tls/22/mypolicy1,myotherpolicy", s) - } - - // process SocketType - if err := checkSockType(split[0]); err != nil { - return result, err - } - result.Type = split[0] - // process port - p, err := strconv.Atoi(split[1]) // port - if err != nil { - return result, err - } - if err := checkSockPort(p); err != nil { - return result, err - } - result.Port = p - - // process policy - result.Policies = []string{} - if len(split) == 3 { - // split the possible multiple policies - splitPolicy := strings.Split(split[2], ",") - // add all the mentioned policies to the result - for _, x := range splitPolicy { - // trim whitespaces and add the result - result.Policies = append(result.Policies, strings.TrimSpace(x)) - } - } - // process host - result.Host = host - return result, nil -} - -// checkSockType checks that the types of the socket definitions from the publish config section -// contains a valid value. -func checkSockType(t string) error { - if _, ok := utils.StringInSlice(supportedSockTypes, t); !ok { - return fmt.Errorf("border0.com does not support socket type %q. Supported types are %q", - t, strings.Join(supportedSockTypes, "|")) - } - return nil -} - -// checkSockPort checks that the port of the socket definitions from the publish config section -// is in the valid range of portnumbers. -func checkSockPort(p int) error { - if p < 1 || p > 65535 { - return fmt.Errorf("incorrect port number %v", p) - } - return nil -} diff --git a/border0_api/border0_test.go b/border0_api/border0_test.go deleted file mode 100644 index 749769c96..000000000 --- a/border0_api/border0_test.go +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package border0_api - -import ( - "context" - "os" - "testing" - "time" - - "github.com/h2non/gock" - "github.com/srl-labs/containerlab/mocks/mocknodes" - "github.com/srl-labs/containerlab/nodes" - "github.com/srl-labs/containerlab/types" - "go.uber.org/mock/gomock" -) - -func TestLogin(t *testing.T) { - type args struct { - email string - password string - } - tests := []struct { - name string - args args - wantErr bool - }{ - { - name: "test login fail", - args: args{ - email: "fgsdfg@dfgsdfg.fr", - password: "1defsdffgd", - }, - wantErr: true, - }, - // // re-add this line and create a seperate file in this folder - // // defining the two variables BORDER0USER and BORDER0PASSWORD with - // // valid border0 credentials to test again live api - // // e.g.: - // // package border0_api - // // const BORDER0USER = "e@mail.address" - // // const BORDER0PASSWORD = "PASSWORD" - // // - // { - // name: "test login fail", - // args: args{ - // email: BORDER0USER, - // password: BORDER0PASSWORD, - // }, - // wantErr: false, - // }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - ctx := context.TODO() - if err := Login(ctx, tt.args.email, tt.args.password, false); (err != nil) != tt.wantErr { - t.Errorf("Login() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_createBorder0Config(t *testing.T) { - type args struct { - ctx context.Context - nodesMap func(mockCtrl *gomock.Controller) map[string]nodes.Node - labname string - } - tests := []struct { - name string - args args - // want func() string - wantErr bool - }{ - { - name: "test ", - args: args{ - ctx: context.TODO(), - nodesMap: getNodeMap, - labname: "MyTinyTestLab", - }, - // want: func() string { - // ssc := StaticSocketsConfig{ - // Connector: &configConnector{ - // Name: "MyTinyTestLab", - // }, - // Credentials: &configCredentials{ - // Token: "SomeValueOtherThenNil", - // }, - // Sockets: []map[string]*configSocket{ - // { - // "clab-TestTopo-node2-tls-22": { - // Port: 22, - // Type: "tls", - // Host: "clab-TestTopo-node2", - // }, - // }, - // { - // "clab-TestTopo-node2-tls-23": { - // Port: 23, - // Type: "tls", - // Host: "clab-TestTopo-node2", - // Policies: []string{"myfunnypolicy"}, - // }, - // }, - // { - // "clab-TestTopo-node5-tls-22": { - // Port: 22, - // Type: "tls", - // Host: "clab-TestTopo-node5", - // }, - // }, - // { - // "clab-TestTopo-node5-tls-25": { - // Port: 25, - // Type: "tls", - // Host: "clab-TestTopo-node5", - // Policies: []string{ - // "test", - // "additionalpolicy", - // }, - // }, - // }, - // }, - // } - // bytesData, err := yaml.Marshal(ssc) - // if err != nil { - // t.Error(err) - // } - // return string(bytesData) - // }, - wantErr: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - // set the token variable first - err := os.Setenv(ENV_NAME_BORDER0_ADMIN_TOKEN, "SomeValueOtherThenNil") - if err != nil { - t.Error(err) - } - - // mock the http client, to be able to inject responses - defer gock.Off() - gock.New(getApiUrl()). - Get("/policies"). - Reply(200). - JSON([]Policy{ - { - ID: "1", - Name: "Foo", - Description: "", - PolicyData: PolicyData{}, - SocketIDs: []string{}, - OrgID: "", - OrgWide: false, - CreatedAt: time.Time{}, - }, - { - ID: "2", - Name: "test", - Description: "", - PolicyData: PolicyData{}, - SocketIDs: []string{}, - OrgID: "", - OrgWide: false, - CreatedAt: time.Time{}, - }, - { - ID: "3", - Name: "additionalpolicy", - Description: "", - PolicyData: PolicyData{}, - SocketIDs: []string{}, - OrgID: "", - OrgWide: false, - CreatedAt: time.Time{}, - }, - { - ID: "4", - Name: "myfunnypolicy", - Description: "", - PolicyData: PolicyData{}, - SocketIDs: []string{}, - OrgID: "", - OrgWide: false, - CreatedAt: time.Time{}, - }, - }) - - // Init Nodes mock - mockCtrl := gomock.NewController(t) - mockerNodes := tt.args.nodesMap(mockCtrl) - - // call function under test - _, err = CreateBorder0Config(tt.args.ctx, mockerNodes, tt.args.labname) - - // signal finish to mock - mockCtrl.Finish() - - if (err != nil) != tt.wantErr { - t.Errorf("createBorder0Config() error = %v, wantErr %v", err, tt.wantErr) - return - } - }) - } -} - -// getNodeMap return a map of nodes for testing purpose. -func getNodeMap(mockCtrl *gomock.Controller) map[string]nodes.Node { - // instantiate Mock Node 1 - mockNode1 := mocknodes.NewMockNode(mockCtrl) - mockNode1.EXPECT().Config().Return( - &types.NodeConfig{ - Image: "alpine:3", - ShortName: "node1", - LongName: "clab-TestTopo-node1", - }, - ).AnyTimes() - - // instantiate Mock Node 2 - mockNode2 := mocknodes.NewMockNode(mockCtrl) - mockNode2.EXPECT().Config().Return( - &types.NodeConfig{ - Image: "alpine:3", - ShortName: "node2", - Stages: &types.Stages{ - Configure: &types.StageConfigure{ - StageBase: types.StageBase{ - WaitFor: types.WaitForList{ - &types.WaitFor{ - Node: "node1", - Stage: types.WaitForCreate, - }, - }, - }, - }, - }, - Publish: []string{ - "tls/22", - "tls/23/myfunnypolicy", - }, - LongName: "clab-TestTopo-node2", - }, - ).AnyTimes() - - // instantiate Mock Node 3 - mockNode3 := mocknodes.NewMockNode(mockCtrl) - mockNode3.EXPECT().Config().Return( - &types.NodeConfig{ - Image: "alpine:3", - NetworkMode: "container:node2", - ShortName: "node3", - Stages: &types.Stages{ - Configure: &types.StageConfigure{ - StageBase: types.StageBase{ - WaitFor: types.WaitForList{ - &types.WaitFor{ - Node: "node1", - Stage: types.WaitForCreate, - }, - &types.WaitFor{ - Node: "node2", - Stage: types.WaitForCreate, - }, - }, - }, - }, - }, - LongName: "clab-TestTopo-node3", - }, - ).AnyTimes() - - // instantiate Mock Node 4 - mockNode4 := mocknodes.NewMockNode(mockCtrl) - mockNode4.EXPECT().Config().Return( - &types.NodeConfig{ - Image: "alpine:3", - MgmtIPv4Address: "172.10.10.1", - ShortName: "node4", - NetworkMode: "container:foobar", - }, - ).AnyTimes() - - // instantiate Mock Node 5 - mockNode5 := mocknodes.NewMockNode(mockCtrl) - mockNode5.EXPECT().Config().Return( - &types.NodeConfig{ - Image: "alpine:3", - MgmtIPv4Address: "172.10.10.2", - ShortName: "node5", - Stages: &types.Stages{ - Configure: &types.StageConfigure{ - StageBase: types.StageBase{ - WaitFor: types.WaitForList{ - &types.WaitFor{ - Node: "node3", - Stage: types.WaitForCreate, - }, - &types.WaitFor{ - Node: "node4", - Stage: types.WaitForCreate, - }, - }, - }, - }, - }, - Publish: []string{ - "tls/22", - "tls/25/test,additionalpolicy", - }, - LongName: "clab-TestTopo-node5", - }, - ).AnyTimes() - - // nodeMap used for testing - nodeMap := map[string]nodes.Node{} - - // nodemap is created with the node definition - for _, x := range []nodes.Node{mockNode1, mockNode2, mockNode3, mockNode4, mockNode5} { - // add node to nodemap - nodeMap[x.Config().ShortName] = x - // add node to dependencyManager - } - - return nodeMap -} diff --git a/border0_api/structs.go b/border0_api/structs.go deleted file mode 100644 index 5a36dd80e..000000000 --- a/border0_api/structs.go +++ /dev/null @@ -1,149 +0,0 @@ -package border0_api - -import ( - "strings" - "time" -) - -type LoginResponse struct { - LoginRefreshResponse - MFA bool `json:"require_mfa"` -} - -type LoginRefreshResponse struct { - Token string `json:"token"` -} - -type LoginRequest struct { - Email string `json:"email"` - Password string `json:"password"` -} - -type ErrorMessage struct { - ErrorMessage string `json:"error_message,omitempty"` -} - -type Socket struct { - Tunnels []Tunnel `json:"tunnels,omitempty"` - Username string `json:"user_name,omitempty"` - SocketID string `json:"socket_id,omitempty"` - SocketTcpPorts []int `json:"socket_tcp_ports,omitempty"` - Dnsname string `json:"dnsname,omitempty"` - Name string `json:"name,omitempty"` - Description string `json:"description,omitempty"` - SocketType string `json:"socket_type,omitempty"` - ProtectedSocket bool `json:"protected_socket"` - ProtectedUsername string `json:"protected_username"` - ProtectedPassword string `json:"protected_password"` - AllowedEmailAddresses []string `json:"cloud_authentication_email_allowed_addressses,omitempty"` - AllowedEmailDomains []string `json:"cloud_authentication_email_allowed_domains,omitempty"` - SSHCa string `json:"ssh_ca,omitempty"` - UpstreamUsername string `json:"upstream_username,omitempty"` - UpstreamPassword string `json:"upstream_password,omitempty"` - UpstreamHttpHostname string `json:"upstream_http_hostname,omitempty"` - UpstreamType string `json:"upstream_type,omitempty"` - CloudAuthEnabled bool `json:"cloud_authentication_enabled,omitempty"` - ConnectorAuthenticationEnabled bool `json:"connector_authentication_enabled,omitempty"` - Tags map[string]string `json:"tags,omitempty"` - CustomDomains []string `json:"custom_domains,omitempty"` - PrivateSocket bool `json:"private_socket"` - PolicyNames []string `json:"policy_names,omitempty"` -} - -func (s *Socket) SanitizeName() { - socketName := strings.ReplaceAll(s.Name, ".", "-") - socketName = strings.ReplaceAll(socketName, " ", "-") - socketName = strings.ReplaceAll(socketName, ".", "-") - s.Name = strings.ReplaceAll(socketName, "_", "-") -} - -type Tunnel struct { - TunnelID string `json:"tunnel_id,omitempty"` - LocalPort int `json:"local_port,omitempty"` - TunnelServer string `json:"tunnel_server,omitempty"` -} - -type StaticSocketsConfig struct { - Connector *configConnector `yaml:"connector"` - Credentials *configCredentials `yaml:"credentials"` - Sockets []map[string]*configSocket `yaml:"sockets"` -} - -type configConnector struct { - Name string `yaml:"name"` -} - -type configCredentials struct { - Token string `yaml:"token"` -} - -type configSocket struct { - Port int `yaml:"port"` - Type string `yaml:"type"` - Host string `yaml:"host"` - Policies []string `yaml:"policies,omitempty"` -} - -type CreatePolicyRequest struct { - Name string `json:"name" binding:"required"` - Description string `json:"description"` - PolicyData PolicyData `json:"policy_data" binding:"required"` - Orgwide bool `json:"org_wide"` -} - -type UpdatePolicyRequest struct { - Name *string `json:"name"` - Description *string `json:"description"` - PolicyData *PolicyData `json:"policy_data" binding:"required"` -} - -type Policy struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - PolicyData PolicyData `json:"policy_data"` - SocketIDs []string `json:"socket_ids"` - OrgID string `json:"org_id"` - OrgWide bool `json:"org_wide"` - CreatedAt time.Time `json:"created_at"` -} - -type PolicyData struct { - Version string `json:"version"` - Action []string `json:"action" mapstructure:"action"` - Condition Condition `json:"condition" mapstructure:"condition"` -} - -type Condition struct { - Who ConditionWho `json:"who,omitempty" mapstructure:"who"` - Where ConditionWhere `json:"where,omitempty" mapstructure:"where"` - When ConditionWhen `json:"when,omitempty" mapstructure:"when"` -} - -type ConditionWho struct { - Email []string `json:"email,omitempty" mapstructure:"email"` - Domain []string `json:"domain,omitempty" mapstructure:"domain"` -} - -type ConditionWhere struct { - AllowedIP []string `json:"allowed_ip,omitempty" mapstructure:"allowed_ip"` - Country []string `json:"country,omitempty" mapstructure:"country"` - CountryNot []string `json:"country_not,omitempty" mapstructure:"country_not"` -} - -type ConditionWhat struct{} - -type ConditionWhen struct { - After string `json:"after,omitempty" mapstructure:"after"` - Before string `json:"before,omitempty" mapstructure:"before"` - TimeOfDayAfter string `json:"time_of_day_after,omitempty" mapstructure:"time_of_day_after"` - TimeOfDayBefore string `json:"time_of_day_before,omitempty" mapstructure:"time_of_day_before"` -} - -type PolicyActionUpdateRequest struct { - Action string `json:"action" binding:"required"` - ID string `json:"id" binding:"required"` -} -type AddSocketToPolicyRequest struct { - Actions []PolicyActionUpdateRequest `json:"actions" binding:"required"` -} diff --git a/clab/config.go b/clab/config.go index 48c92990d..659aed01d 100644 --- a/clab/config.go +++ b/clab/config.go @@ -188,7 +188,6 @@ func (c *CLab) createNodeCfg(nodeName string, nodeDef *types.NodeDefinition, idx NetworkMode: strings.ToLower(c.Config.Topology.GetNodeNetworkMode(nodeName)), MgmtIPv4Address: nodeDef.GetMgmtIPv4(), MgmtIPv6Address: nodeDef.GetMgmtIPv6(), - Publish: c.Config.Topology.GetNodePublish(nodeName), Sysctls: c.Config.Topology.GetSysCtl(nodeName), Sandbox: c.Config.Topology.GetNodeSandbox(nodeName), Kernel: c.Config.Topology.GetNodeKernel(nodeName), diff --git a/clab/register.go b/clab/register.go index 215058511..ded297b9c 100644 --- a/clab/register.go +++ b/clab/register.go @@ -5,7 +5,6 @@ package clab import ( - border0 "github.com/srl-labs/containerlab/nodes/border0" bridge "github.com/srl-labs/containerlab/nodes/bridge" c8000 "github.com/srl-labs/containerlab/nodes/c8000" ceos "github.com/srl-labs/containerlab/nodes/ceos" @@ -93,7 +92,6 @@ func (c *CLab) RegisterNodes() { xrd.Register(c.Reg) rare.Register(c.Reg) c8000.Register(c.Reg) - border0.Register(c.Reg) k8s_kind.Register(c.Reg) cisco_iol.Register(c.Reg) huawei_vrp.Register(c.Reg) diff --git a/cmd/border0.go b/cmd/border0.go deleted file mode 100644 index 4fb263d02..000000000 --- a/cmd/border0.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2020 Nokia -// Licensed under the BSD 3-Clause License. -// SPDX-License-Identifier: BSD-3-Clause - -package cmd - -import ( - "context" - - "github.com/spf13/cobra" - "github.com/srl-labs/containerlab/border0_api" -) - -var ( - border0Email string - border0Password string - - border0DisableBrowser bool -) - -func init() { - toolsCmd.AddCommand(border0Cmd) - - border0Cmd.AddCommand(border0LoginCmd) - - border0LoginCmd.Flags().BoolVarP(&border0DisableBrowser, "disable-browser", "b", false, "Disable opening the browser") - - // Programmatic user authentication for the Border0 service was deprecated on 11/2023, - // so we hide the email and password flags though we keep them around for backwards - // compatibility because some containerlabs users have been allowlisted for programmatic - // authentication. Note that as of 12/2023 no new allowlist requests are considered by - // Border0. Instead Border0 users who wish to integrate with containerlabs will need to - // use Border0 "admin tokens" i.e. service identities. For info on how to create tokens, - // see https://docs.border0.com/docs/creating-access-token - border0LoginCmd.Flags().StringVarP(&border0Email, "email", "e", "", "Email address") - border0LoginCmd.Flags().StringVarP(&border0Password, "password", "p", "", "Password") - border0LoginCmd.Flags().MarkHidden("email") - border0LoginCmd.Flags().MarkHidden("password") -} - -// border0Cmd represents the border0 command container. -var border0Cmd = &cobra.Command{ - Use: "border0", - Short: "border0.com commands", -} - -// border0LoginCmd represents the border0-login command. -var border0LoginCmd = &cobra.Command{ - Use: "login", - Short: "Logs in to border0.com service and saves a token to current working directory", - - RunE: func(cmd *cobra.Command, args []string) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - return border0_api.Login(ctx, border0Email, border0Password, border0DisableBrowser) - }, -} diff --git a/docs/cmd/tools/border0/login.md b/docs/cmd/tools/border0/login.md deleted file mode 100644 index 4954ab1da..000000000 --- a/docs/cmd/tools/border0/login.md +++ /dev/null @@ -1,31 +0,0 @@ -# border0 login - -### Description - -The `login` sub-command under the `tools border0` command performs a login to border0.com service and saves the acquired authentication token[^1]. - -The token is saved as `$PWD/.border0_token` file. - -### Usage - -`containerlab tools border0 login [local-flags]` - -### Flags - -#### disable-browser -The `--disable-browser | -b` prevents the command from attempting to open the browser in order to complete authentication with Border0. If the flag is set, the command will simply print the URL which you must navigate to, whether in the same device or a different device, in order to complete authentication. - -### Examples - -```bash -containerlab tools border0 login - -Please navigate to the URL below in order to complete the login process: -https://portal.border0.com/login?device_identifier=IjM1OTJkZGVmLTgzNTMtNDU4Yy04NjNkLTk1OTdhYjY0ZjFiOSI.ZW6BRw.Z9XlL0CtL7HkKTDX7GSp28d9mG0 - -Login successful - -INFO[0000] Written border0.com token to a file /root/containerlab/.border0_token -``` - -[^1]: Authentication token is used to [publish ports](../../../manual/published-ports.md) of a containerlab nodes. \ No newline at end of file diff --git a/docs/cmd/tools/mysocketio/login.md b/docs/cmd/tools/mysocketio/login.md deleted file mode 100644 index e333001c8..000000000 --- a/docs/cmd/tools/mysocketio/login.md +++ /dev/null @@ -1,31 +0,0 @@ -# Mysocketio login - -### Description - -The `login` sub-command under the `tools mysocketio` command performs a login to mysocketio service and saves the acquired authentication token[^1]. - -The token is saved as `$PWD/.mysocketio_token` file. - -### Usage - -`containerlab tools mysocketio login [local-flags]` - -### Flags - -#### disable-browser -The `--disable-browser | -b` prevents the command from attempting to open the browser in order to complete authentication with Border0. If the flag is set, the command will simply print the URL which you must navigate to, whether in the same device or a different device, in order to complete authentication. - -### Examples - -```bash -containerlab tools mysocketio login - -Please navigate to the URL below in order to complete the login process: -https://portal.border0.com/login?device_identifier=IjM1OTJkZGVmLTgzNTMtNDU4Yy04NjNkLTk1OTdhYjY0ZjFiOSI.ZW6BRw.Z9XlL0CtL7HkKTDX7GSp28d9mG0 - -Login successful - -INFO[0000] Written mysocketio token to a file /root/containerlab/.mysocketio_token -``` - -[^1]: Authentication token is used to [publish ports](../../../manual/published-ports.md) of a containerlab nodes. \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index d28208a14..6705f2781 100644 --- a/docs/index.md +++ b/docs/index.md @@ -101,7 +101,7 @@ This short clip briefly demonstrates containerlab features and explains its purp * **Labs and demos** Containerlab was meant to be a tool for provisioning networking labs built with containers. It is free, open and ubiquitous. No software apart from Docker is required! As with any lab environment it allows the users to validate features, topologies, perform interop testing, datapath testing, etc. - It is also a perfect companion for your next demo. Deploy the lab fast, with all its configuration stored as a code -> destroy when done. Easily and [securely share lab access](manual/published-ports.md) if needed. + It is also a perfect companion for your next demo. Deploy the lab fast, with all its configuration stored as a code -> destroy when done. * **Testing and CI** Because of the containerlab's single-binary packaging and code-based lab definition files, it was never that easy to spin up a test bed for CI. Gitlab CI, Github Actions and virtually any CI system will be able to spin up containerlab topologies in a single simple command. * **Telemetry validation** diff --git a/docs/manual/kinds/linux.md b/docs/manual/kinds/linux.md index 3411138ea..514f5fb91 100644 --- a/docs/manual/kinds/linux.md +++ b/docs/manual/kinds/linux.md @@ -5,6 +5,7 @@ search: # Linux container + Labs deployed with containerlab are endlessly flexible, mostly because containerlab can spin up and wire regular containers as part of the lab topology. Nowadays more and more workloads are packaged into containers, and containerlab users can nicely integrate them in their labs following a familiar docker' compose-like syntax. As long as the networking domain is considered, the most common use case for bare linux containers is to introduce "clients" or traffic generators which are connected to the network nodes or host telemetry/monitoring stacks. @@ -14,6 +15,7 @@ Nowadays more and more workloads are packaged into containers, and containerlab But, of course, you are free to choose which container to add into your lab, there is not restriction to that! ## Using linux containers + As with any other node, the linux container is a node of a specific kind, `linux` in this case. ```yaml @@ -42,7 +44,6 @@ Containerlab tries to deliver the same level of flexibility in container configu * [env](../nodes.md#env) - to set environment variables * [user](../nodes.md#user) - to set a user that will be used inside the container system * [cmd](../nodes.md#cmd) - to provide a command that will be executed when the container is started -* [publish](../nodes.md#publish) - to expose container' service via [border0.com integration](../published-ports.md) !!!note Nodes of `linux` kind will have a `on-failure` restart policy when run with docker runtime. This means that if container fails/exits with a non zero return code, docker will restart this container automatically. diff --git a/docs/manual/nodes.md b/docs/manual/nodes.md index 15b24e49b..d6abbbc86 100644 --- a/docs/manual/nodes.md +++ b/docs/manual/nodes.md @@ -579,24 +579,6 @@ topology: - some-opt ``` -### publish - -Container lab integrates with [border0.com](https://border0.com) service to allow for private, Internet-reachable tunnels created for ports of containerlab nodes. This enables effortless access sharing with customers/partners/colleagues. - -This integration is extensively covered on [Publish ports](published-ports.md) page. - -```yaml -name: demo -topology: - nodes: - r1: - kind: nokia_srlinux - publish: - - tcp/22 # tcp port 22 will be published - - tcp/57400 # tcp port 57400 will be published - - http/8080 # http port 8080 will be published -``` - ### network-mode By default containerlab nodes use bridge-mode driver - nodes are created with their first interface connected to a docker network (management network). diff --git a/docs/manual/published-ports.md b/docs/manual/published-ports.md deleted file mode 100644 index e3741ff25..000000000 --- a/docs/manual/published-ports.md +++ /dev/null @@ -1,141 +0,0 @@ - -Labs are typically deployed in the isolated environments, such as company's internal network, cloud region or even a laptop. The lab nodes can happily talk to each other and, if needed, can reach Internet in the outbound direction. - -But sometimes it is really needed to let your lab nodes be reachable over Internet securely and privately in the inbound direction. There are many use cases that warrant such _publishing_, some of the most common are: - -* create a lab in your environment and share it with a customer/colleague on-demand -* make an interactive demo/training where nodes are shared with an audience for hands-on experience -* share a private lab with someone to collaborate or troubleshoot -* expose management interfaces (gNMI, NETCONF, SNMP) to test integration with collectors deployed outside of your lab environment - -Check out the short video demonstrating the integration: - - - -Containerlab made all of these use cases possible by integrating with [border0.com](https://border0.com) service. border0.com provides personal and secure tunnels for https/tls/tcp/ssh ports over global anycast[^1] network spanning US, Europe and Asia. - -To make a certain port of a node available via border0.com tunnel provide a `publish` container under the node/kind/default section of the topology: - -```yaml -name: demo -topology: - nodes: - r1: - kind: nokia_srlinux - publish: - # tcp port 22 will be published and accessible to anyone - - tcp/22 - # tcp port 57400 will be published using a specific EXISTING border0.com policy - - tls/57400/MyBorder0Policy - # http service running over 10200 will be published - # considering the two existing policies MyBorder0Policy and MyOtherPolicy - - http/10200/MyBorder0Policy,MyOtherPolicy -``` - - - -## Registration - -Tunnels set up by border0.com are associated with a user who sets them, thus users are required to register within the service. Luckily, the registration is a split second process carried out via a [web portal](https://portal.border0.com/register). All it takes is an email and a password (or an existing google / github account). - -## Acquiring a token - -To authenticate with border0.com service a user needs to acquire the token by logging into the service. A helper command [`border0 login`](../cmd/tools/border0/login.md) has been added to containerlab to help with that: - -```bash -# Login with password entered from the prompt -containerlab tools border0 login -e myemail@dot.com -Password: -INFO[0000] Written border0 token to a file /root/containerlab/.border0_token -``` - -The acquired token will be saved under `.border0_token` filename in the current working directory. - -!!!info - The token is valid for 5 hours, once the token expires, the already established tunnels will continue to work, but to establish new tunnels a new token must be provided. - -## Specify what to share - -To indicate which ports to publish a users needs to add the `publish` section under the node/kind or default level of the [topology definition file](topo-def-file.md). In the example below, we are publishing SSH and gNMI services of `r1` node: - -```yaml -name: demo -topology: - nodes: - r1: - kind: nokia_srlinux - publish: - - tls/22 # tcp port 22 will be exposed - - tls/57400 # tcp port 57400 will be exposed -``` - -The `publish` section holds a list of `/[/` strings, where - -* `` must be one of the supported border0.com socket type[^2] - http/https/tls/ssh -!!!note - The ssh type is special in a way, that the border0.com service terminates the ssh connection to provide recording / replay capabilities etc. but therefore requires injection of an SSH-CA into the lab nodes, for the border0.com service to be able to establish the proxied ssh session to the containers. Therefore the tls kind should commonly be the right choise for the type. -* `` must be a single valid port value -* `` an optional element restricting access to published ports based on border0.com defined policies [border0.com policies](#border0com-policies) section. - -!!!note - For a regular border0.com account the maximum number of tunnels is limited to: - - tls based tunnels: 5 - - http based tunnels: 10 - If >5 tcp tunnels are required users should launch a container / VM in a lab, expose it's SSH service and use it as a jumphost. - -## Add border0.com node - -Containerlab integrates with border0.com service by leveraging a [container](https://github.com/srl-labs/containerlab-border0.com) that has the border0.com client binary integrated. In order for the ports indicated in the `publish` block to be published, a user needs to add a `border0` node to the topology. The complete topology file could look like this: - -```yaml -name: publish -topology: - nodes: - r1: - kind: nokia_srlinux - image: ghcr.io/nokia/srlinux - publish: - - tls/22 # tcp port 22 will be exposed - - grafana: - kind: linux - image: grafana/grafana:7.4.3 - publish: - - http/3000 # grafana' default http port will be published - - # adding mysocketio container which has border0 client packaged - border0: - kind: border0 -``` - -The `border0` node is a tiny linux container with border0 client installed. Containerlab uses this node to create the sockets and start the tunnels as per `publish` block instructions. - -Internally containerlab utilizes the Static Sockets Plugin to provide the necessary configuration to the border0 process. - -## Border0.com policies - -Policies are used to control who has access to what Sockets and under what conditions. Think of policies as advanced, Identity-, application-aware, and context-aware firewall rules. Unlike traditional firewalls or access control list (ACL) rules, Border0 policies allow you to define access rules based on Identity, time of day, application type, and location. (see [https://docs.border0.com/docs/policies]) - -Within the `publish` block it is possible to provide a list of comma separated policy names which will be attached to the published port in question. - -Authentication is carried out by OAuth via Google or GitHub providers. - -Once authenticated via any of the available providers the sessions will establish. - -### TCP/TLS - -With Identity Aware sockets used for SSH[^4] service, a client must have [border0](https://docs.border0.com/docs/quick-start) client installed and use the following command to establish a connection: - -```bash -ssh @ \ - -o 'ProxyCommand=border0 client tls --host %h' -``` - -As with HTTP services, a browser page will appear asking to proceed with authentication. Upon successful authentication, the SSH session will establish. diff --git a/docs/rn/0.12.0.md b/docs/rn/0.12.0.md index 647047ae7..a327c2ee8 100644 --- a/docs/rn/0.12.0.md +++ b/docs/rn/0.12.0.md @@ -1,8 +1,10 @@ # Release 0.12.0 + :material-calendar: 2021-03-28 ## Identity aware sockets -A major improvement to our ["published ports"](../manual/published-ports.md) feature has landed in 0.12.0. Now it is possible to create Identity Aware sockets for any port of your lab. + +A major improvement to our "published ports" feature has landed in 0.12.0. Now it is possible to create Identity Aware sockets for any port of your lab. [Identity Aware sockets](https://www.mysocket.io/post/introducing-identity-aware-sockets-enabling-zero-trust-access-for-your-private-services) is a feature of mysocketio service that allows you to let in only authorized users. Authorization is possible via multiple OAuth providers and can be configured to let in users with specific emails and/or with specific domains. @@ -17,34 +19,40 @@ Check out how easy it is to create identity aware tunnels with containerlab: With this enhancement it is now possible to create long-running secure tunnels which only the authorized users will be able to access. - ## On-demand veth plumbing + Sometimes it is needed to add some additional connections after the lab has been deployed. Although the labs are quickly to re-spin, sometimes one find themselves in the middle of the use case configuration and there is a need to add another connection between the nodes. With [`tools veth`](../cmd/tools/veth/create.md) command it is now possible to add veth pairs between container<->container, containers<->host and containers<->bridge nodes. Now you can add modify your lab connections without redeploying the entire lab[^2]. ## Safer ways to write clab files + Containerlab got its own JSON Schema that governs the structure of the topology definition files. If you name your topo file as `*.clab.yml` then some editors like VS Code will automatically provide auto-suggestions and linting for your clab files. Yes, from now on we will call our topo files as **clab-files**. ## Create TLS certificates effortlessly + With the new commands [`tools cert ca create`](../cmd/tools/cert/ca/create.md) and [`tools cert sign`](../cmd/tools/cert/sign.md) it is now possible to create CA and node certificates with just two commands embedded into containerlab. Start [from here](../manual/cert.md) if you always wanted to be able to reduce the number of openssl commands. We also [added a lab](https://clabs.netdevops.me/security/gnmitls/) that pictures the net positive effect of having those commands when creating TLS certificates for secured gNMI connectivity. ## Ansible inventory + It is imperative to create a nice automation flow that goes from infra deployment to the subsequent configuration automation. When containerlab finishes its deployment job [we now create an ansible inventory file](../manual/inventory.md) for a deployed lab. With this inventory file the users can start their configuration management playbooks and configure the lab for a use case in mind. ## Smart MTU for management network + Default MTU value for the management network will now be inherited from the MTU of the `docker0` bridge interface. Thus, if you configured your docker daemon for a custom MTU, it will be respected by containerlab. ## Running nodes in `bridge` network + When management network name is set to `bridge`, containerlab nodes will be run in the default docker network named `bridge`. This is the network where containers end up connecting when you do `docker run`, so running the lab in the default docker network makes it easy to connect your lab with the workloads that have been started by someone else. ## Releases notification + When a new release comes out we let you know next time you deploy a lab, a tiny message will pop up in the log saying that a new one is ready to make your labs even more efficient and easy. ``` @@ -53,6 +61,7 @@ Run 'containerlab version upgrade' to upgrade or go check other installation opt ``` ## Saving SR OS config + SR OS nodes which are launched with [`vr-sros`](../manual/kinds/vr-sros.md) kind now have support for saving their configuration with [`containerlab save`](../cmd/save.md) command. This is implemented via Netconf's `` RPC that is executed against SR OS node. @@ -61,6 +70,6 @@ This is implemented via Netconf's `` RPC that is executed against S 1. Added support for user-defined [node labels](../manual/nodes.md#labels) which can convey metadata for a given node. 2. Container node needs to support live interface attachment. -3. New TLS certificate logic for SR Linux nodes. If the CA files and node cert exist, the re-deployment of a lab won't issue new certificates and will reuse the existing ones. +3. New TLS certificate logic for SR Linux nodes. If the CA files and node cert exist, the re-deployment of a lab won't issue new certificates and will reuse the existing ones. 4. Additional node property [`network-mode`](../manual/nodes.md#network-mode) has been added which allows to deploy the node in the host networking mode. 5. If the changes containerlab makes to LLDP/TX-offload on the management bridge fail, they won't prevent the lab from proceed deploying. diff --git a/docs/rn/0.19.md b/docs/rn/0.19.md index eca65b577..375208fe1 100644 --- a/docs/rn/0.19.md +++ b/docs/rn/0.19.md @@ -18,7 +18,7 @@ With the help of [@chriscummings-esnet](https://github.com/chriscummings-esnet) ## Mysocket support for HTTP proxies -Our famous [mysocketio integration](../manual/published-ports.md) that allows you to share labs effortlessly and secure has been enhanced with proxy support. +Our famous mysocketio integration that allows you to share labs effortlessly and secure has been enhanced with proxy support. With HTTP proxy support it is now possible to share lab access in the environments that have external SSH access blocked. diff --git a/go.mod b/go.mod index 94688183a..4c2d058dd 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,6 @@ go 1.22.1 require ( github.com/a8m/envsubst v1.4.2 github.com/awalterschulze/gographviz v2.0.3+incompatible - github.com/cenkalti/backoff v2.2.1+incompatible github.com/containernetworking/plugins v1.5.1 github.com/containers/common v0.60.4 github.com/containers/podman/v5 v5.2.5 @@ -16,12 +15,10 @@ require ( github.com/docker/go-units v0.5.0 github.com/dustin/go-humanize v1.0.1 github.com/florianl/go-tc v0.4.4 - github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/go-cmp v0.6.0 github.com/google/nftables v0.2.0 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.6.0 - github.com/h2non/gock v1.2.0 github.com/hairyhenderson/gomplate/v3 v3.11.8 github.com/hashicorp/go-version v1.7.0 github.com/jedib0t/go-pretty/v6 v6.6.5 @@ -36,7 +33,6 @@ require ( github.com/pmorjan/kmod v1.1.1 github.com/scrapli/scrapligo v1.3.3 github.com/sirupsen/logrus v1.9.3 - github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 github.com/spf13/cobra v1.8.1 github.com/steiler/acls v0.1.1 github.com/stretchr/testify v1.10.0 @@ -91,7 +87,6 @@ require ( github.com/google/go-containerregistry v0.20.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 // indirect - github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/kr/fs v0.1.0 // indirect github.com/letsencrypt/boulder v0.0.0-20240418210053-89b07f4543e0 // indirect diff --git a/go.sum b/go.sum index d2cb12b38..e4a38a4d0 100644 --- a/go.sum +++ b/go.sum @@ -165,8 +165,6 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee h1:BnPxIde0gjtTnc9Er7cxvBk8DHLWhEux0SxayC8dP6I= github.com/c2h5oh/datasize v0.0.0-20200112174442-28bbd4740fee/go.mod h1:S/7n9copUssQ56c7aAgHqftWO4LTf4xY6CGWt8Bc+3M= -github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M= github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -526,8 +524,6 @@ github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -668,10 +664,6 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.5 h1:UImYN5qQ8tuGpGE16ZmjvcTtTw24zw1 github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 h1:/c3QmbOGMGTOumP2iT/rCwB7b0QDGLKzqOmktBjT+Is= github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1/go.mod h1:5SN9VR2LTsRFsrEC6FHgRbTWrTHu6tqPeKxEQv15giM= -github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE= -github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw= -github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hairyhenderson/go-fsimpl v0.0.0-20220529183339-9deae3e35047 h1:nSSfN9G8O8XXDqB3aDEHJ8K+0llYYToNlTcWOe1Pti8= github.com/hairyhenderson/go-fsimpl v0.0.0-20220529183339-9deae3e35047/go.mod h1:30RY4Ey+bg+BGKBufZE2IEmxk7hok9U9mjdgZYomwN4= github.com/hairyhenderson/gomplate/v3 v3.11.8 h1:T63wLRk+Y9C601ChYa/+FZ30XT/UEWydMDZhOOJM3K0= @@ -957,8 +949,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4= -github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms= github.com/nbutton23/zxcvbn-go v0.0.0-20180912185939-ae427f1e4c1d/go.mod h1:o96djdrsSGy3AWPyBgZMAGfxZNfgntdJG+11KU4QvbU= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= github.com/networkop/ignite v0.0.0-20240730102642-a3f2f53f9abc h1:5Cp/ljSn+ponXZbfdmXpDaAyBEL1X0HVA0EfXwbqKC4= @@ -1140,8 +1130,6 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA= -github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w= github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= diff --git a/mkdocs.yml b/mkdocs.yml index f94c6d7a7..4ef697576 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -70,7 +70,6 @@ nav: - Quickstart: manual/clabernetes/quickstart.md - Packet capture in c9s: manual/clabernetes/pcap.md - Node filtering: manual/node-filtering.md - - Publish ports: manual/published-ports.md - Multi-node labs: manual/multi-node.md - Certificate management: manual/cert.md - Inventory: manual/inventory.md diff --git a/nodes/border0/border0.go b/nodes/border0/border0.go deleted file mode 100644 index 16737df49..000000000 --- a/nodes/border0/border0.go +++ /dev/null @@ -1,121 +0,0 @@ -package border0 - -import ( - "context" - "fmt" - "path" - - log "github.com/sirupsen/logrus" - "github.com/srl-labs/containerlab/border0_api" - "github.com/srl-labs/containerlab/clab/exec" - "github.com/srl-labs/containerlab/nodes" - "github.com/srl-labs/containerlab/types" - "github.com/srl-labs/containerlab/utils" -) - -var Kindnames = []string{"border0"} - -func Register(r *nodes.NodeRegistry) { - r.Register(Kindnames, func() nodes.Node { - return new(border0) - }, nil) -} - -type border0 struct { - nodes.DefaultNode - topologyName string - hostborder0yamlPath string - containerborder0yamlPath string -} - -func (b *border0) Init(cfg *types.NodeConfig, opts ...nodes.NodeOption) error { - // Init DefaultNode - b.DefaultNode = *nodes.NewDefaultNode(b) - - b.Cfg = cfg - for _, o := range opts { - o(b) - } - - // set default image - if b.Cfg.Image == "" { - b.Cfg.Image = "ghcr.io/srl-labs/containerlab-border0.com:latest" - } - - // prepare host and container paths for border0.com config bind - b.hostborder0yamlPath = path.Join(b.Cfg.LabDir, "border0.yaml") - b.containerborder0yamlPath = path.Join("/code", "border0.yaml") - return nil -} - -func (b *border0) CheckDeploymentConditions(ctx context.Context) error { - // perform the default checks defined in the DefaultNode - err := b.DefaultNode.CheckDeploymentConditions(ctx) - if err != nil { - return err - } - // perform a border0 login refresh to validate border0.com token - err = border0_api.RefreshLogin(ctx) - if err != nil { - return err - } - // TODO: @steiler, check referenced policies do exist - // therefor we would either need the map[string]node.nodes here or a - // more specific container config returning the PUBLISH setting - // - // The idea would be to come up with a specific interface (DeploymentConditionsChecker) implemented by the Clab struct. - // which implements GetNodes() or as mentioned more specific that allows for the retrieval of the required information. - return nil -} - -func (b *border0) PreDeploy(_ context.Context, params *nodes.PreDeployParams) error { - utils.CreateDirectory(b.Cfg.LabDir, 0777) - - // setup the mount for the Static Socket Plugin config file - b.topologyName = params.TopologyName - border0Mount := fmt.Sprintf("%s:%s:ro", b.hostborder0yamlPath, b.containerborder0yamlPath) - b.Cfg.Binds = append(b.Cfg.Binds, border0Mount) - - // make sure the config file exists on the host prior to starting the container - err := utils.CreateFile(b.hostborder0yamlPath, "") - if err != nil { - return err - } - return nil -} - -func (b *border0) PostDeploy(ctx context.Context, params *nodes.PostDeployParams) error { - // disable tx offloading - log.Debugf("Running postdeploy actions for border0.com node %q", b.Cfg.ShortName) - - err := b.ExecFunction(ctx, utils.NSEthtoolTXOff(b.GetShortName(), "eth0")) - if err != nil { - log.Error(err) - } - - log.Infof("Creating border0.com tunnels...") - // create a configuration for border0.com - config, err := border0_api.CreateBorder0Config(ctx, params.Nodes, b.topologyName) - if err != nil { - return err - } - // write the config to the mounted file - utils.CreateFile(b.hostborder0yamlPath, config) - - // bring up the tunnels - b0Cmd := exec.NewExecCmdFromSlice([]string{ - "/bin/sh", "-c", - "./border0 connector start --config " + b.containerborder0yamlPath + " 2> /proc/1/fd/2 1> /proc/1/fd/1", - }) - err = b.Runtime.ExecNotWait(ctx, b.Cfg.LongName, b0Cmd) - if err != nil { - return err - } - return nil -} - -func (b *border0) Delete(ctx context.Context) error { - // deleting container - return b.DefaultNode.Delete(ctx) - // TODO: maybe deleting sockets as well; we would need infos about the nodes -} diff --git a/types/node_definition.go b/types/node_definition.go index a1ed0321c..69bdead0c 100644 --- a/types/node_definition.go +++ b/types/node_definition.go @@ -40,8 +40,6 @@ type NodeDefinition struct { MgmtIPv4 string `yaml:"mgmt-ipv4,omitempty"` // user-defined IPv6 address in the management network MgmtIPv6 string `yaml:"mgmt-ipv6,omitempty"` - // list of ports to publish with mysocketctl - Publish []string `yaml:"publish,omitempty"` // environment variables Env map[string]string `yaml:"env,omitempty"` // external file containing environment variables @@ -256,13 +254,6 @@ func (n *NodeDefinition) GetMgmtIPv6() string { return n.MgmtIPv6 } -func (n *NodeDefinition) GetPublish() []string { - if n == nil { - return nil - } - return n.Publish -} - func (n *NodeDefinition) GetEnv() map[string]string { if n == nil { return nil diff --git a/types/topology.go b/types/topology.go index 3f4ec3943..f2857e3e8 100644 --- a/types/topology.go +++ b/types/topology.go @@ -138,21 +138,6 @@ func (t *Topology) GetNodeEnvFiles(name string) []string { return nil } -func (t *Topology) GetNodePublish(name string) []string { - if ndef, ok := t.Nodes[name]; ok { - if len(ndef.GetPublish()) > 0 { - return ndef.GetPublish() - } - if kdef, ok := t.Kinds[ndef.GetKind()]; ok && kdef != nil { - if len(kdef.GetPublish()) > 0 { - return kdef.GetPublish() - } - } - return t.Defaults.GetPublish() - } - return nil -} - func (t *Topology) GetNodeLabels(name string) map[string]string { if ndef, ok := t.Nodes[name]; ok { return utils.MergeStringMaps(t.Defaults.GetLabels(), diff --git a/types/types.go b/types/types.go index c32723f30..3b2d4a8bb 100644 --- a/types/types.go +++ b/types/types.go @@ -165,9 +165,6 @@ type NodeConfig struct { Healthcheck *HealthcheckConfig // Network aliases Aliases []string `json:"aliases,omitempty"` - // NSPath string `json:"nspath,omitempty"` // network namespace path for this node - // list of ports to publish with mysocketctl - Publish []string `json:"publish,omitempty"` // Extra /etc/hosts entries for all nodes. ExtraHosts []string `json:"extra-hosts,omitempty"` Labels map[string]string `json:"labels,omitempty"` // container labels From c1d6f074062f582e56e071d8ea46bb32c5043b86 Mon Sep 17 00:00:00 2001 From: Roman Dodin Date: Fri, 17 Jan 2025 10:41:46 +0100 Subject: [PATCH 2/2] remove broken link --- docs/rn/0.39.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/rn/0.39.md b/docs/rn/0.39.md index c026003e8..0c5bf4a73 100644 --- a/docs/rn/0.39.md +++ b/docs/rn/0.39.md @@ -98,4 +98,4 @@ We are happy to announce that [RARE](../manual/kinds/rare-freertr.md) Network OS ## Securely connecting to labs with Border0 (experimental) -With mysocketio rebranding to border0 we had to revisit APIs for creating secured remote access to labs. This is an experimental feature and is not yet fully supported. Please see [this](../manual/published-ports.md) page for more details. #1131 +With mysocketio rebranding to border0 we had to revisit APIs for creating secured remote access to labs. This is an experimental feature and is not yet fully supported. #1131