Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add ssh channel exector #51

Merged
merged 1 commit into from
Sep 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package main

import (
"github.com/chaosblade-io/chaosblade-exec-os/exec/model"
"log"
"os"

Expand Down Expand Up @@ -50,6 +51,8 @@ func getModels() *spec.Models {
}
specModels := make([]*spec.Models, 0)
for _, modeSpec := range modelCommandSpecs {
flagSpecs := append(modeSpec.Flags(), model.GetSSHExpFlags()...)
modeSpec.SetFlags(flagSpecs)
specModel := util.ConvertSpecToModels(modeSpec, spec.ExpPrepareModel{}, "host")
specModels = append(specModels, specModel)
}
Expand Down
213 changes: 213 additions & 0 deletions exec/executor_ssh.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
/*
* Copyright 1999-2020 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package exec

import (
"bufio"
"context"
"fmt"
"github.com/chaosblade-io/chaosblade-exec-os/version"
"github.com/chaosblade-io/chaosblade-spec-go/spec"
"golang.org/x/crypto/ssh"
"net"
"os"
"strconv"
"strings"
"time"
)

const (
BladeBin = "/opt/chaosblade/blade"
DefaultSSHPort = 22
BladeReleaseURL = "https://chaosblade.oss-cn-hangzhou.aliyuncs.com/agent/github/%s/chaosblade-%s-linux-amd64.tar.gz"
)

// support ssh channel flags
var ChannelFlag = &spec.ExpFlag{
Name: "channel",
Desc: "Select the channel for execution, and you can now select SSH",
NoArgs: false,
Required: false,
}

var SSHHostFlag = &spec.ExpFlag{
Name: "ssh-host",
Desc: "Use this flag when the channel is ssh",
NoArgs: false,
Required: false,
}

var SSHUserFlag = &spec.ExpFlag{
Name: "ssh-user",
Desc: "Use this flag when the channel is ssh",
NoArgs: false,
Required: false,
}

var SSHPortFlag = &spec.ExpFlag{
Name: "ssh-port",
Desc: "Use this flag when the channel is ssh",
NoArgs: false,
Required: false,
}

var BladeRelease = &spec.ExpFlag{
Name: "blade-release",
Desc: "Blade release package,use this flag when the channel is ssh",
NoArgs: false,
Required: false,
}

type SSHExecutor struct {
spec.Executor
}

func NewSSHExecutor() spec.Executor {
return &SSHExecutor{}
}

func (*SSHExecutor) Name() string {
return "ssh"
}

func (e *SSHExecutor) SetChannel(channel spec.Channel) {
}

func (e *SSHExecutor) Exec(uid string, ctx context.Context, expModel *spec.ExpModel) *spec.Response {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Please enter password:")
password, _ := reader.ReadString('\n')

port := DefaultSSHPort
portStr := expModel.ActionFlags[SSHPortFlag.Name]
if portStr != "" {
var err error
port, err = strconv.Atoi(portStr)
if err != nil || port < 1 {
return spec.ReturnFail(spec.Code[spec.IllegalParameters], "--port value must be a positive integer")
}
}

client := &SSHClient{
Host: expModel.ActionFlags[SSHHostFlag.Name],
Username: expModel.ActionFlags[SSHUserFlag.Name],
Password: strings.Replace(password, "\n", "", -1),
Port: port,
}

matchers := spec.ConvertExpMatchersToString(expModel, func() map[string]spec.Empty {
return excludeSSHFlags()
})

if _, ok := spec.IsDestroy(ctx); ok {
str, err := client.Run(fmt.Sprintf("%s destroy %s", BladeBin, uid))
if err != nil {
return spec.ReturnFail(spec.Code[spec.ExecCommandError], err.Error())
}
return spec.Decode(str, nil)
} else {
bladeReleaseURL := expModel.ActionFlags[BladeRelease.Name]
if bladeReleaseURL == "" {
bladeReleaseURL = fmt.Sprintf(BladeReleaseURL, version.BladeVersion, version.BladeVersion)
}
assembly :=
fmt.Sprintf(`if [ ! -f "/opt/chaosblade/blade" ];then
if [ ! -d "/opt" ];then
mkdir /opt
fi
wget %s
tar -zxf $(echo "%s" |awk -F '/' '{print $NF}') -C /opt
mv /opt/$(tar tf $(echo "%s" |awk -F '/' '{print $NF}') | head -1 | cut -f1 -d/) /opt/chaosblade
fi`, bladeReleaseURL, bladeReleaseURL, bladeReleaseURL)
_, err := client.Run(assembly)
if err != nil {
return spec.ReturnFail(spec.Code[spec.ExecCommandError], err.Error())
}

execute := fmt.Sprintf("%s create %s %s %s --uid %s -d", BladeBin, expModel.Target, expModel.ActionName, matchers, uid)
output, err := client.Run(execute)
if err != nil {
return spec.ReturnFail(spec.Code[spec.ExecCommandError], err.Error())
}
return spec.Decode(output, nil)
}

}

type SSHClient struct {
Host string
Username string
Password string
Port int
client *ssh.Client
cipherList []string
}

func (c SSHClient) Run(shell string) (string, error) {
if c.client == nil {
if err := c.connect(); err != nil {
return "", err
}
}
session, err := c.client.NewSession()
if err != nil {
return "", err
}
defer session.Close()
buf, err := session.CombinedOutput(shell)
return string(buf), err
}

func (c *SSHClient) connect() error {

var config ssh.Config
if len(c.cipherList) == 0 {
config = ssh.Config{
Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-gcm@openssh.com", "arcfour256", "arcfour128", "aes128-cbc", "3des-cbc", "aes192-cbc", "aes256-cbc"},
}
} else {
config = ssh.Config{
Ciphers: c.cipherList,
}
}

clientConfig := ssh.ClientConfig{
User: c.Username,
Config: config,
Auth: []ssh.AuthMethod{ssh.Password(c.Password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
Timeout: 10 * time.Second,
}
sshClient, err := ssh.Dial("tcp", fmt.Sprintf("%s:%d", c.Host, c.Port), &clientConfig)
if err != nil {
return err
}
c.client = sshClient
return nil
}

func excludeSSHFlags() map[string]spec.Empty {
flags := make(map[string]spec.Empty, 0)
flags[ChannelFlag.Name] = spec.Empty{}
flags[SSHHostFlag.Name] = spec.Empty{}
flags[SSHUserFlag.Name] = spec.Empty{}
flags[SSHPortFlag.Name] = spec.Empty{}
flags[BladeRelease.Name] = spec.Empty{}
return flags
}
13 changes: 13 additions & 0 deletions exec/model/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,16 @@ func GetAllOsExecutors() map[string]spec.Executor {
for key, value := range executorMap {
executors[key] = value
}
expFlagSpecs := append(expModel.Flags(), GetSSHExpFlags()...)
expModel.SetFlags(expFlagSpecs)
}
return executors
}

func GetSHHExecutor() spec.Executor {
return exec.NewSSHExecutor()
}

// GetAllExpModels returns the experiment model specs in the project.
// Support for other project about chaosblade
func GetAllExpModels() []spec.ExpModelCommandSpec {
Expand All @@ -56,3 +62,10 @@ func ExtractExecutorFromExpModel(expModel spec.ExpModelCommandSpec) map[string]s
}
return executors
}

func GetSSHExpFlags() []spec.ExpFlagSpec {
flags := []spec.ExpFlagSpec{
exec.ChannelFlag, exec.SSHHostFlag, exec.SSHPortFlag, exec.SSHUserFlag, exec.BladeRelease,
}
return flags
}
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ require (
github.com/shirou/gopsutil v2.20.5+incompatible
github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect
github.com/sirupsen/logrus v1.5.0
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,8 @@ go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947M/+gp0+CqQXDtMRC0fseo=
golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
Expand Down
20 changes: 20 additions & 0 deletions version/version.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright 1999-2019 Alibaba Group Holding Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package version

// Default version is latest, you can specify the value at compile time, see Makefile in chaosblade project for the details
var BladeVersion = "latest"