mirror of
https://github.com/harness/drone.git
synced 2025-05-12 06:59:54 +08:00
Merge pull request #1967 from bradrydzewski/master
Promote new agent <> communication
This commit is contained in:
commit
b05b54a2ef
22
.drone.yml
22
.drone.yml
@ -22,17 +22,17 @@ pipeline:
|
|||||||
when:
|
when:
|
||||||
event: push
|
event: push
|
||||||
|
|
||||||
|
archive:
|
||||||
# archive:
|
image: plugins/s3
|
||||||
# image: plugins/s3
|
acl: public-read
|
||||||
# acl: public-read
|
bucket: downloads.drone.io
|
||||||
# bucket: downloads.drone.io
|
source: release/**/*.*
|
||||||
# source: release/**/*.*
|
target: /0.6.0/
|
||||||
# access_key: ${AWS_ACCESS_KEY_ID}
|
access_key: ${AWS_ACCESS_KEY_ID}
|
||||||
# secret_key: ${AWS_SECRET_ACCESS_KEY}
|
secret_key: ${AWS_SECRET_ACCESS_KEY}
|
||||||
# when:
|
when:
|
||||||
# event: push
|
event: push
|
||||||
# branch: master
|
branch: master
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
|
@ -1 +1 @@
|
|||||||
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lL2Ryb25lCgpwaXBlbGluZToKICB0ZXN0OgogICAgaW1hZ2U6IGdvbGFuZzoxLjgKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPMTVWRU5ET1JFWFBFUklNRU5UPTEKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgZGVwcyBnZW4KICAgICAgLSBtYWtlIHRlc3QgdGVzdF9wb3N0Z3JlcyB0ZXN0X215c3FsCgogIGNvbXBpbGU6CiAgICBpbWFnZTogZ29sYW5nOjEuOAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gR08xNVZFTkRPUkVYUEVSSU1FTlQ9MQogICAgICAtIEdPUEFUSD0vZ28KICAgIGNvbW1hbmRzOgogICAgICAtIGV4cG9ydCBQQVRIPSRQQVRIOiRHT1BBVEgvYmluCiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IHB1c2gKCgogICMgYXJjaGl2ZToKICAjICAgaW1hZ2U6IHBsdWdpbnMvczMKICAjICAgYWNsOiBwdWJsaWMtcmVhZAogICMgICBidWNrZXQ6IGRvd25sb2Fkcy5kcm9uZS5pbwogICMgICBzb3VyY2U6IHJlbGVhc2UvKiovKi4qCiAgIyAgIGFjY2Vzc19rZXk6ICR7QVdTX0FDQ0VTU19LRVlfSUR9CiAgIyAgIHNlY3JldF9rZXk6ICR7QVdTX1NFQ1JFVF9BQ0NFU1NfS0VZfQogICMgICB3aGVuOgogICMgICAgIGV2ZW50OiBwdXNoCiAgIyAgICAgYnJhbmNoOiBtYXN0ZXIKCiAgcHVibGlzaDoKICAgIGltYWdlOiBwbHVnaW5zL2RvY2tlcgogICAgcmVwbzogZHJvbmUvZHJvbmUKICAgIHVzZXJuYW1lOiAke0RPQ0tFUl9VU0VSTkFNRX0KICAgIHBhc3N3b3JkOiAke0RPQ0tFUl9QQVNTV09SRH0KICAgIHRhZzogWyAwLjYgXQogICAgd2hlbjoKICAgICAgYnJhbmNoOiBtYXN0ZXIKICAgICAgZXZlbnQ6IHB1c2gKCiAgbm90aWZ5OgogICAgaW1hZ2U6IHBsdWdpbnMvZ2l0dGVyCiAgICB3ZWJob29rOiAke0dJVFRFUl9XRUJIT09LfQogICAgd2hlbjoKICAgICAgc3RhdHVzOiBbIHN1Y2Nlc3MsIGZhaWx1cmUgXQogICAgICBldmVudDogWyBwdXNoLCBwdWxsX3JlcXVlc3QgXQoKc2VydmljZXM6CiAgcG9zdGdyZXM6CiAgICBpbWFnZTogcG9zdGdyZXM6OS40LjUKICAgIGVudmlyb25tZW50OgogICAgICAtIFBPU1RHUkVTX1VTRVI9cG9zdGdyZXMKICBteXNxbDoKICAgIGltYWdlOiBteXNxbDo1LjYuMjcKICAgIGVudmlyb25tZW50OgogICAgICAtIE1ZU1FMX0RBVEFCQVNFPXRlc3QKICAgICAgLSBNWVNRTF9BTExPV19FTVBUWV9QQVNTV09SRD15ZXMK.ewtVFWTXLuf3sGDbSdt5RloiAm8jdZIzqHp1M5wd8z8
|
eyJhbGciOiJIUzI1NiJ9.d29ya3NwYWNlOgogIGJhc2U6IC9nbwogIHBhdGg6IHNyYy9naXRodWIuY29tL2Ryb25lL2Ryb25lCgpwaXBlbGluZToKICB0ZXN0OgogICAgaW1hZ2U6IGdvbGFuZzoxLjgKICAgIGVudmlyb25tZW50OgogICAgICAtIEdPMTVWRU5ET1JFWFBFUklNRU5UPTEKICAgIGNvbW1hbmRzOgogICAgICAtIG1ha2UgZGVwcyBnZW4KICAgICAgLSBtYWtlIHRlc3QgdGVzdF9wb3N0Z3JlcyB0ZXN0X215c3FsCgogIGNvbXBpbGU6CiAgICBpbWFnZTogZ29sYW5nOjEuOAogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gR08xNVZFTkRPUkVYUEVSSU1FTlQ9MQogICAgICAtIEdPUEFUSD0vZ28KICAgIGNvbW1hbmRzOgogICAgICAtIGV4cG9ydCBQQVRIPSRQQVRIOiRHT1BBVEgvYmluCiAgICAgIC0gbWFrZSBidWlsZAogICAgd2hlbjoKICAgICAgZXZlbnQ6IHB1c2gKCiAgYXJjaGl2ZToKICAgIGltYWdlOiBwbHVnaW5zL3MzCiAgICBhY2w6IHB1YmxpYy1yZWFkCiAgICBidWNrZXQ6IGRvd25sb2Fkcy5kcm9uZS5pbwogICAgc291cmNlOiByZWxlYXNlLyoqLyouKgogICAgdGFyZ2V0OiAvMC42LjAvCiAgICBhY2Nlc3Nfa2V5OiAke0FXU19BQ0NFU1NfS0VZX0lEfQogICAgc2VjcmV0X2tleTogJHtBV1NfU0VDUkVUX0FDQ0VTU19LRVl9CiAgICB3aGVuOgogICAgICBldmVudDogcHVzaAogICAgICBicmFuY2g6IG1hc3RlcgoKICBwdWJsaXNoOgogICAgaW1hZ2U6IHBsdWdpbnMvZG9ja2VyCiAgICByZXBvOiBkcm9uZS9kcm9uZQogICAgdXNlcm5hbWU6ICR7RE9DS0VSX1VTRVJOQU1FfQogICAgcGFzc3dvcmQ6ICR7RE9DS0VSX1BBU1NXT1JEfQogICAgdGFnOiBbIDAuNiBdCiAgICB3aGVuOgogICAgICBicmFuY2g6IG1hc3RlcgogICAgICBldmVudDogcHVzaAoKICBub3RpZnk6CiAgICBpbWFnZTogcGx1Z2lucy9naXR0ZXIKICAgIHdlYmhvb2s6ICR7R0lUVEVSX1dFQkhPT0t9CiAgICB3aGVuOgogICAgICBzdGF0dXM6IFsgc3VjY2VzcywgZmFpbHVyZSBdCiAgICAgIGV2ZW50OiBbIHB1c2gsIHB1bGxfcmVxdWVzdCBdCgpzZXJ2aWNlczoKICBwb3N0Z3JlczoKICAgIGltYWdlOiBwb3N0Z3Jlczo5LjQuNQogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gUE9TVEdSRVNfVVNFUj1wb3N0Z3JlcwogIG15c3FsOgogICAgaW1hZ2U6IG15c3FsOjUuNi4yNwogICAgZW52aXJvbm1lbnQ6CiAgICAgIC0gTVlTUUxfREFUQUJBU0U9dGVzdAogICAgICAtIE1ZU1FMX0FMTE9XX0VNUFRZX1BBU1NXT1JEPXllcwo.-w5oLW1ORcBymNExC1Q-y5Ju6P0-9D2GKRjaTFKP3vg
|
329
agent/agent.go
329
agent/agent.go
@ -1,329 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/drone/drone/build"
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/drone/drone/version"
|
|
||||||
"github.com/drone/drone/yaml"
|
|
||||||
"github.com/drone/drone/yaml/transform"
|
|
||||||
"github.com/drone/envsubst"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Write(*build.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Agent struct {
|
|
||||||
Update UpdateFunc
|
|
||||||
Logger LoggerFunc
|
|
||||||
Engine build.Engine
|
|
||||||
Timeout time.Duration
|
|
||||||
Platform string
|
|
||||||
Namespace string
|
|
||||||
Extension []string
|
|
||||||
Escalate []string
|
|
||||||
Netrc []string
|
|
||||||
Local string
|
|
||||||
Pull bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) Poll() error {
|
|
||||||
|
|
||||||
// logrus.Infof("Starting build %s/%s#%d.%d",
|
|
||||||
// payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// logrus.Infof("Finished build %s/%s#%d.%d",
|
|
||||||
// payload.Repo.Owner, payload.Repo.Name, payload.Build.Number, payload.Job.Number)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) Run(payload *model.Work, cancel <-chan bool) error {
|
|
||||||
|
|
||||||
payload.Job.Status = model.StatusRunning
|
|
||||||
payload.Job.Started = time.Now().Unix()
|
|
||||||
|
|
||||||
spec, err := a.prep(payload)
|
|
||||||
if err != nil {
|
|
||||||
payload.Job.Error = err.Error()
|
|
||||||
payload.Job.ExitCode = 255
|
|
||||||
payload.Job.Finished = payload.Job.Started
|
|
||||||
payload.Job.Status = model.StatusError
|
|
||||||
a.Update(payload)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
a.Update(payload)
|
|
||||||
err = a.exec(spec, payload, cancel)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
payload.Job.ExitCode = 255
|
|
||||||
payload.Job.Error = err.Error()
|
|
||||||
}
|
|
||||||
if exitErr, ok := err.(*build.ExitError); ok {
|
|
||||||
payload.Job.ExitCode = exitErr.Code
|
|
||||||
payload.Job.Error = "" // exit errors are already written to the log
|
|
||||||
}
|
|
||||||
|
|
||||||
payload.Job.Finished = time.Now().Unix()
|
|
||||||
|
|
||||||
switch payload.Job.ExitCode {
|
|
||||||
case 128, 130, 137:
|
|
||||||
payload.Job.Status = model.StatusKilled
|
|
||||||
case 0:
|
|
||||||
payload.Job.Status = model.StatusSuccess
|
|
||||||
default:
|
|
||||||
payload.Job.Status = model.StatusFailure
|
|
||||||
}
|
|
||||||
|
|
||||||
a.Update(payload)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) prep(w *model.Work) (*yaml.Config, error) {
|
|
||||||
|
|
||||||
envs := toEnv(w)
|
|
||||||
envSecrets := map[string]string{}
|
|
||||||
|
|
||||||
// list of secrets to interpolate in the yaml
|
|
||||||
for _, secret := range w.Secrets {
|
|
||||||
if (w.Verified || secret.SkipVerify) && secret.MatchEvent(w.Build.Event) {
|
|
||||||
envSecrets[secret.Name] = secret.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var err error
|
|
||||||
w.Yaml, err = envsubst.Eval(w.Yaml, func(s string) string {
|
|
||||||
env, ok := envSecrets[s]
|
|
||||||
if !ok {
|
|
||||||
env, _ = envs[s]
|
|
||||||
}
|
|
||||||
if strings.Contains(env, "\n") {
|
|
||||||
env = fmt.Sprintf("%q", env)
|
|
||||||
}
|
|
||||||
return env
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// append secrets when verified or when a secret does not require
|
|
||||||
// verification
|
|
||||||
var secrets []*model.Secret
|
|
||||||
for _, secret := range w.Secrets {
|
|
||||||
if w.Verified || secret.SkipVerify {
|
|
||||||
secrets = append(secrets, secret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// inject the netrc file into the clone plugin if the repository is
|
|
||||||
// private and requires authentication.
|
|
||||||
if w.Repo.IsPrivate {
|
|
||||||
secrets = append(secrets, &model.Secret{
|
|
||||||
Name: "DRONE_NETRC_USERNAME",
|
|
||||||
Value: w.Netrc.Login,
|
|
||||||
Images: []string{"*"},
|
|
||||||
Events: []string{"*"},
|
|
||||||
})
|
|
||||||
secrets = append(secrets, &model.Secret{
|
|
||||||
Name: "DRONE_NETRC_PASSWORD",
|
|
||||||
Value: w.Netrc.Password,
|
|
||||||
Images: []string{"*"},
|
|
||||||
Events: []string{"*"},
|
|
||||||
})
|
|
||||||
secrets = append(secrets, &model.Secret{
|
|
||||||
Name: "DRONE_NETRC_MACHINE",
|
|
||||||
Value: w.Netrc.Machine,
|
|
||||||
Images: []string{"*"},
|
|
||||||
Events: []string{"*"},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
conf, err := yaml.ParseString(w.Yaml)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
src := "src"
|
|
||||||
if url, _ := url.Parse(w.Repo.Link); url != nil {
|
|
||||||
host, _, err := net.SplitHostPort(url.Host)
|
|
||||||
if err == nil {
|
|
||||||
url.Host = host
|
|
||||||
}
|
|
||||||
src = filepath.Join(src, url.Host, url.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
transform.Clone(conf, w.Repo.Kind)
|
|
||||||
transform.Environ(conf, envs)
|
|
||||||
transform.DefaultFilter(conf)
|
|
||||||
if w.BuildLast != nil {
|
|
||||||
transform.ChangeFilter(conf, w.BuildLast.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
transform.ImageSecrets(conf, secrets, w.Build.Event)
|
|
||||||
transform.Identifier(conf)
|
|
||||||
transform.WorkspaceTransform(conf, "/drone", src)
|
|
||||||
|
|
||||||
if err := transform.Check(conf, w.Repo.IsTrusted); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
transform.CommandTransform(conf)
|
|
||||||
transform.ImagePull(conf, a.Pull)
|
|
||||||
transform.ImageTag(conf)
|
|
||||||
if err := transform.ImageEscalate(conf, a.Escalate); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
transform.PluginParams(conf)
|
|
||||||
|
|
||||||
if a.Local != "" {
|
|
||||||
transform.PluginDisable(conf, true)
|
|
||||||
transform.ImageVolume(conf, []string{a.Local + ":" + conf.Workspace.Path})
|
|
||||||
}
|
|
||||||
|
|
||||||
transform.Pod(conf, a.Platform)
|
|
||||||
if err := transform.RemoteTransform(conf, a.Extension); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return conf, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *Agent) exec(spec *yaml.Config, payload *model.Work, cancel <-chan bool) error {
|
|
||||||
|
|
||||||
conf := build.Config{
|
|
||||||
Engine: a.Engine,
|
|
||||||
Buffer: 500,
|
|
||||||
}
|
|
||||||
|
|
||||||
pipeline := conf.Pipeline(spec)
|
|
||||||
defer pipeline.Teardown()
|
|
||||||
|
|
||||||
// setup the build environment
|
|
||||||
if err := pipeline.Setup(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
replacer := NewSecretReplacer(payload.Secrets)
|
|
||||||
timeout := time.After(time.Duration(payload.Repo.Timeout) * time.Minute)
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-pipeline.Done():
|
|
||||||
return pipeline.Err()
|
|
||||||
case <-cancel:
|
|
||||||
pipeline.Stop()
|
|
||||||
return fmt.Errorf("termination request received, build cancelled")
|
|
||||||
case <-timeout:
|
|
||||||
pipeline.Stop()
|
|
||||||
return fmt.Errorf("maximum time limit exceeded, build cancelled")
|
|
||||||
case <-time.After(a.Timeout):
|
|
||||||
pipeline.Stop()
|
|
||||||
return fmt.Errorf("terminal inactive for %v, build cancelled", a.Timeout)
|
|
||||||
case <-pipeline.Next():
|
|
||||||
|
|
||||||
// TODO(bradrydzewski) this entire block of code should probably get
|
|
||||||
// encapsulated in the pipeline.
|
|
||||||
status := model.StatusSuccess
|
|
||||||
if pipeline.Err() != nil {
|
|
||||||
status = model.StatusFailure
|
|
||||||
}
|
|
||||||
// updates the build status passed into each container. I realize this is
|
|
||||||
// a bit out of place and will work to resolve.
|
|
||||||
pipeline.Head().Environment["DRONE_BUILD_STATUS"] = status
|
|
||||||
|
|
||||||
if !pipeline.Head().Constraints.Match(
|
|
||||||
a.Platform,
|
|
||||||
payload.Build.Deploy,
|
|
||||||
payload.Build.Event,
|
|
||||||
payload.Build.Branch,
|
|
||||||
status, payload.Job.Environment) { // TODO: fix this whole section
|
|
||||||
|
|
||||||
pipeline.Skip()
|
|
||||||
} else {
|
|
||||||
pipeline.Exec()
|
|
||||||
}
|
|
||||||
case line := <-pipeline.Pipe():
|
|
||||||
line.Out = replacer.Replace(line.Out)
|
|
||||||
a.Logger(line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func toEnv(w *model.Work) map[string]string {
|
|
||||||
envs := map[string]string{
|
|
||||||
"CI": "drone",
|
|
||||||
"DRONE": "true",
|
|
||||||
"DRONE_ARCH": "linux/amd64",
|
|
||||||
"DRONE_REPO": w.Repo.FullName,
|
|
||||||
"DRONE_REPO_SCM": w.Repo.Kind,
|
|
||||||
"DRONE_REPO_OWNER": w.Repo.Owner,
|
|
||||||
"DRONE_REPO_NAME": w.Repo.Name,
|
|
||||||
"DRONE_REPO_LINK": w.Repo.Link,
|
|
||||||
"DRONE_REPO_AVATAR": w.Repo.Avatar,
|
|
||||||
"DRONE_REPO_BRANCH": w.Repo.Branch,
|
|
||||||
"DRONE_REPO_PRIVATE": fmt.Sprintf("%v", w.Repo.IsPrivate),
|
|
||||||
"DRONE_REPO_TRUSTED": fmt.Sprintf("%v", w.Repo.IsTrusted),
|
|
||||||
"DRONE_REMOTE_URL": w.Repo.Clone,
|
|
||||||
"DRONE_COMMIT_SHA": w.Build.Commit,
|
|
||||||
"DRONE_COMMIT_REF": w.Build.Ref,
|
|
||||||
"DRONE_COMMIT_REFSPEC": w.Build.Refspec,
|
|
||||||
"DRONE_COMMIT_BRANCH": w.Build.Branch,
|
|
||||||
"DRONE_COMMIT_LINK": w.Build.Link,
|
|
||||||
"DRONE_COMMIT_MESSAGE": w.Build.Message,
|
|
||||||
"DRONE_COMMIT_AUTHOR": w.Build.Author,
|
|
||||||
"DRONE_COMMIT_AUTHOR_EMAIL": w.Build.Email,
|
|
||||||
"DRONE_COMMIT_AUTHOR_AVATAR": w.Build.Avatar,
|
|
||||||
"DRONE_BUILD_NUMBER": fmt.Sprintf("%d", w.Build.Number),
|
|
||||||
"DRONE_BUILD_EVENT": w.Build.Event,
|
|
||||||
"DRONE_BUILD_STATUS": w.Build.Status,
|
|
||||||
"DRONE_BUILD_LINK": fmt.Sprintf("%s/%s/%d", w.System.Link, w.Repo.FullName, w.Build.Number),
|
|
||||||
"DRONE_BUILD_CREATED": fmt.Sprintf("%d", w.Build.Created),
|
|
||||||
"DRONE_BUILD_STARTED": fmt.Sprintf("%d", w.Build.Started),
|
|
||||||
"DRONE_BUILD_FINISHED": fmt.Sprintf("%d", w.Build.Finished),
|
|
||||||
"DRONE_JOB_NUMBER": fmt.Sprintf("%d", w.Job.Number),
|
|
||||||
"DRONE_JOB_STATUS": w.Job.Status,
|
|
||||||
"DRONE_JOB_ERROR": w.Job.Error,
|
|
||||||
"DRONE_JOB_EXIT_CODE": fmt.Sprintf("%d", w.Job.ExitCode),
|
|
||||||
"DRONE_JOB_STARTED": fmt.Sprintf("%d", w.Job.Started),
|
|
||||||
"DRONE_JOB_FINISHED": fmt.Sprintf("%d", w.Job.Finished),
|
|
||||||
"DRONE_YAML_VERIFIED": fmt.Sprintf("%v", w.Verified),
|
|
||||||
"DRONE_YAML_SIGNED": fmt.Sprintf("%v", w.Signed),
|
|
||||||
"DRONE_BRANCH": w.Build.Branch,
|
|
||||||
"DRONE_COMMIT": w.Build.Commit,
|
|
||||||
"DRONE_VERSION": version.Version,
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.Build.Event == model.EventTag {
|
|
||||||
envs["DRONE_TAG"] = strings.TrimPrefix(w.Build.Ref, "refs/tags/")
|
|
||||||
}
|
|
||||||
if w.Build.Event == model.EventPull {
|
|
||||||
envs["DRONE_PULL_REQUEST"] = pullRegexp.FindString(w.Build.Ref)
|
|
||||||
}
|
|
||||||
if w.Build.Event == model.EventDeploy {
|
|
||||||
envs["DRONE_DEPLOY_TO"] = w.Build.Deploy
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.BuildLast != nil {
|
|
||||||
envs["DRONE_PREV_BUILD_STATUS"] = w.BuildLast.Status
|
|
||||||
envs["DRONE_PREV_BUILD_NUMBER"] = fmt.Sprintf("%v", w.BuildLast.Number)
|
|
||||||
envs["DRONE_PREV_COMMIT_SHA"] = w.BuildLast.Commit
|
|
||||||
}
|
|
||||||
|
|
||||||
// inject matrix values as environment variables
|
|
||||||
for key, val := range w.Job.Environment {
|
|
||||||
envs[key] = val
|
|
||||||
}
|
|
||||||
return envs
|
|
||||||
}
|
|
||||||
|
|
||||||
var pullRegexp = regexp.MustCompile("\\d+")
|
|
@ -1,46 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SecretReplacer hides secrets from being exposed by the build output.
|
|
||||||
type SecretReplacer interface {
|
|
||||||
// Replace conceals instances of secrets found in s.
|
|
||||||
Replace(s string) string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSecretReplacer creates a SecretReplacer based on whether any value in
|
|
||||||
// secrets requests it be hidden.
|
|
||||||
func NewSecretReplacer(secrets []*model.Secret) SecretReplacer {
|
|
||||||
var r []string
|
|
||||||
for _, s := range secrets {
|
|
||||||
if s.Conceal {
|
|
||||||
r = append(r, s.Value, "*****")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r) == 0 {
|
|
||||||
return &noopReplacer{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &secretReplacer{
|
|
||||||
replacer: strings.NewReplacer(r...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type noopReplacer struct{}
|
|
||||||
|
|
||||||
func (*noopReplacer) Replace(s string) string {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
type secretReplacer struct {
|
|
||||||
replacer *strings.Replacer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *secretReplacer) Replace(s string) string {
|
|
||||||
return r.replacer.Replace(s)
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/franela/goblin"
|
|
||||||
)
|
|
||||||
|
|
||||||
const testString = "This is SECRET: secret_value"
|
|
||||||
|
|
||||||
func TestSecret(t *testing.T) {
|
|
||||||
g := goblin.Goblin(t)
|
|
||||||
g.Describe("SecretReplacer", func() {
|
|
||||||
g.It("Should conceal secret", func() {
|
|
||||||
secrets := []*model.Secret{
|
|
||||||
{
|
|
||||||
Name: "SECRET",
|
|
||||||
Value: "secret_value",
|
|
||||||
Conceal: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
r := NewSecretReplacer(secrets)
|
|
||||||
g.Assert(r.Replace(testString)).Equal("This is SECRET: *****")
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("Should not conceal secret", func() {
|
|
||||||
secrets := []*model.Secret{
|
|
||||||
{
|
|
||||||
Name: "SECRET",
|
|
||||||
Value: "secret_value",
|
|
||||||
Conceal: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
r := NewSecretReplacer(secrets)
|
|
||||||
g.Assert(r.Replace(testString)).Equal(testString)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,67 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/build"
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/drone/mq/logger"
|
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UpdateFunc handles buid pipeline status updates.
|
|
||||||
type UpdateFunc func(*model.Work)
|
|
||||||
|
|
||||||
// LoggerFunc handles buid pipeline logging updates.
|
|
||||||
type LoggerFunc func(*build.Line)
|
|
||||||
|
|
||||||
var NoopUpdateFunc = func(*model.Work) {}
|
|
||||||
|
|
||||||
var TermLoggerFunc = func(line *build.Line) {
|
|
||||||
fmt.Println(line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClientUpdater returns an updater that sends updated build details
|
|
||||||
// to the drone server.
|
|
||||||
func NewClientUpdater(client *stomp.Client) UpdateFunc {
|
|
||||||
return func(w *model.Work) {
|
|
||||||
err := client.SendJSON("/queue/updates", w)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warningf("Error updating %s/%s#%d.%d. %s",
|
|
||||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number, err)
|
|
||||||
}
|
|
||||||
if w.Job.Status != model.StatusRunning {
|
|
||||||
var dest = fmt.Sprintf("/topic/logs.%d", w.Job.ID)
|
|
||||||
var opts = []stomp.MessageOption{
|
|
||||||
stomp.WithHeader("eof", "true"),
|
|
||||||
stomp.WithRetain("all"),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := client.Send(dest, []byte("eof"), opts...); err != nil {
|
|
||||||
logger.Warningf("Error sending eof %s/%s#%d.%d. %s",
|
|
||||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClientLogger(client *stomp.Client, id int64, limit int64) LoggerFunc {
|
|
||||||
|
|
||||||
var size int64
|
|
||||||
var dest = fmt.Sprintf("/topic/logs.%d", id)
|
|
||||||
var opts = []stomp.MessageOption{
|
|
||||||
stomp.WithRetain("all"),
|
|
||||||
}
|
|
||||||
|
|
||||||
return func(line *build.Line) {
|
|
||||||
if size > limit {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := client.SendJSON(dest, line, opts...); err != nil {
|
|
||||||
logrus.Errorf("Error streaming build logs. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
size += int64(len(line.Out))
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,48 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import "github.com/drone/drone/yaml"
|
|
||||||
|
|
||||||
// Config defines the configuration for creating the Pipeline.
|
|
||||||
type Config struct {
|
|
||||||
Engine Engine
|
|
||||||
|
|
||||||
// Buffer defines the size of the buffer for the channel to which the
|
|
||||||
// console output is streamed.
|
|
||||||
Buffer uint
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipeline creates a build Pipeline using the specific configuration for
|
|
||||||
// the given Yaml specification.
|
|
||||||
func (c *Config) Pipeline(spec *yaml.Config) *Pipeline {
|
|
||||||
|
|
||||||
pipeline := Pipeline{
|
|
||||||
engine: c.Engine,
|
|
||||||
pipe: make(chan *Line, c.Buffer),
|
|
||||||
next: make(chan error),
|
|
||||||
done: make(chan error),
|
|
||||||
}
|
|
||||||
|
|
||||||
var containers []*yaml.Container
|
|
||||||
containers = append(containers, spec.Services...)
|
|
||||||
containers = append(containers, spec.Pipeline...)
|
|
||||||
|
|
||||||
for _, c := range containers {
|
|
||||||
if c.Disabled {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
next := &element{Container: c}
|
|
||||||
if pipeline.head == nil {
|
|
||||||
pipeline.head = next
|
|
||||||
pipeline.tail = next
|
|
||||||
} else {
|
|
||||||
pipeline.tail.next = next
|
|
||||||
pipeline.tail = next
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
pipeline.next <- nil
|
|
||||||
}()
|
|
||||||
|
|
||||||
return &pipeline
|
|
||||||
}
|
|
@ -1,112 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/drone/drone/build"
|
|
||||||
"github.com/drone/drone/build/docker/internal"
|
|
||||||
"github.com/drone/drone/yaml"
|
|
||||||
|
|
||||||
"github.com/samalba/dockerclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
type dockerEngine struct {
|
|
||||||
client dockerclient.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dockerEngine) ContainerStart(container *yaml.Container) (string, error) {
|
|
||||||
conf := toContainerConfig(container)
|
|
||||||
auth := toAuthConfig(container)
|
|
||||||
|
|
||||||
// pull the image if it does not exists or if the Container
|
|
||||||
// is configured to always pull a new image.
|
|
||||||
_, err := e.client.InspectImage(container.Image)
|
|
||||||
if err != nil || container.Pull {
|
|
||||||
e.client.PullImage(container.Image, auth)
|
|
||||||
}
|
|
||||||
|
|
||||||
// create and start the container and return the Container ID.
|
|
||||||
id, err := e.client.CreateContainer(conf, container.ID, auth)
|
|
||||||
if err != nil {
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
err = e.client.StartContainer(id, &conf.HostConfig)
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
// remove the container if it cannot be started
|
|
||||||
e.client.RemoveContainer(id, true, true)
|
|
||||||
return id, err
|
|
||||||
}
|
|
||||||
return id, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dockerEngine) ContainerStop(id string) error {
|
|
||||||
e.client.StopContainer(id, 1)
|
|
||||||
e.client.KillContainer(id, "9")
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dockerEngine) ContainerRemove(id string) error {
|
|
||||||
e.client.StopContainer(id, 1)
|
|
||||||
e.client.KillContainer(id, "9")
|
|
||||||
e.client.RemoveContainer(id, true, true)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dockerEngine) ContainerWait(id string) (*build.State, error) {
|
|
||||||
// wait for the container to exit
|
|
||||||
//
|
|
||||||
// TODO(bradrydzewski) we should have a for loop here
|
|
||||||
// to re-connect and wait if this channel returns a
|
|
||||||
// result even though the container is still running.
|
|
||||||
//
|
|
||||||
<-e.client.Wait(id)
|
|
||||||
v, err := e.client.InspectContainer(id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &build.State{
|
|
||||||
ExitCode: v.State.ExitCode,
|
|
||||||
OOMKilled: v.State.OOMKilled,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *dockerEngine) ContainerLogs(id string) (io.ReadCloser, error) {
|
|
||||||
opts := &dockerclient.LogOptions{
|
|
||||||
Follow: true,
|
|
||||||
Stdout: true,
|
|
||||||
Stderr: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
piper, pipew := io.Pipe()
|
|
||||||
go func() {
|
|
||||||
defer pipew.Close()
|
|
||||||
|
|
||||||
// sometimes the docker logs fails due to parsing errors. this
|
|
||||||
// routine will check for such a failure and attempt to resume
|
|
||||||
// if necessary.
|
|
||||||
for i := 0; i < 5; i++ {
|
|
||||||
if i > 0 {
|
|
||||||
opts.Tail = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
rc, err := e.client.ContainerLogs(id, opts)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
// use Docker StdCopy
|
|
||||||
internal.StdCopy(pipew, pipew, rc)
|
|
||||||
|
|
||||||
// check to see if the container is still running. If not,
|
|
||||||
// we can safely exit and assume there are no more logs left
|
|
||||||
// to stream.
|
|
||||||
v, err := e.client.InspectContainer(id)
|
|
||||||
if err != nil || !v.State.Running {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
return piper, nil
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package docker
|
|
@ -1,25 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/drone/drone/build"
|
|
||||||
"github.com/samalba/dockerclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewClient returns a new Docker engine using the provided Docker client.
|
|
||||||
func NewClient(client dockerclient.Client) build.Engine {
|
|
||||||
return &dockerEngine{client}
|
|
||||||
}
|
|
||||||
|
|
||||||
// New returns a new Docker engine from the provided DOCKER_HOST and
|
|
||||||
// DOCKER_CERT_PATH environment variables.
|
|
||||||
func New(host, cert string, tls bool) (build.Engine, error) {
|
|
||||||
config, err := dockerclient.TLSConfigFromCertPath(cert)
|
|
||||||
if err == nil && tls {
|
|
||||||
config.InsecureSkipVerify = true
|
|
||||||
}
|
|
||||||
client, err := dockerclient.NewDockerClient(host, config)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return NewClient(client), nil
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package docker
|
|
@ -1 +0,0 @@
|
|||||||
This is an internal copy of the Docker stdcopy package that removes the logrus debug logging. The original package is found at https://github.com/docker/docker/tree/master/pkg/stdcopy
|
|
@ -1,167 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StdType is the type of standard stream
|
|
||||||
// a writer can multiplex to.
|
|
||||||
type StdType byte
|
|
||||||
|
|
||||||
const (
|
|
||||||
// Stdin represents standard input stream type.
|
|
||||||
Stdin StdType = iota
|
|
||||||
// Stdout represents standard output stream type.
|
|
||||||
Stdout
|
|
||||||
// Stderr represents standard error steam type.
|
|
||||||
Stderr
|
|
||||||
|
|
||||||
stdWriterPrefixLen = 8
|
|
||||||
stdWriterFdIndex = 0
|
|
||||||
stdWriterSizeIndex = 4
|
|
||||||
|
|
||||||
startingBufLen = 32*1024 + stdWriterPrefixLen + 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// stdWriter is wrapper of io.Writer with extra customized info.
|
|
||||||
type stdWriter struct {
|
|
||||||
io.Writer
|
|
||||||
prefix byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write sends the buffer to the underneath writer.
|
|
||||||
// It insert the prefix header before the buffer,
|
|
||||||
// so stdcopy.StdCopy knows where to multiplex the output.
|
|
||||||
// It makes stdWriter to implement io.Writer.
|
|
||||||
func (w *stdWriter) Write(buf []byte) (n int, err error) {
|
|
||||||
if w == nil || w.Writer == nil {
|
|
||||||
return 0, errors.New("Writer not instantiated")
|
|
||||||
}
|
|
||||||
if buf == nil {
|
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix}
|
|
||||||
binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(buf)))
|
|
||||||
|
|
||||||
line := append(header[:], buf...)
|
|
||||||
|
|
||||||
n, err = w.Writer.Write(line)
|
|
||||||
n -= stdWriterPrefixLen
|
|
||||||
|
|
||||||
if n < 0 {
|
|
||||||
n = 0
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStdWriter instantiates a new Writer.
|
|
||||||
// Everything written to it will be encapsulated using a custom format,
|
|
||||||
// and written to the underlying `w` stream.
|
|
||||||
// This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
|
|
||||||
// `t` indicates the id of the stream to encapsulate.
|
|
||||||
// It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr.
|
|
||||||
func NewStdWriter(w io.Writer, t StdType) io.Writer {
|
|
||||||
return &stdWriter{
|
|
||||||
Writer: w,
|
|
||||||
prefix: byte(t),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StdCopy is a modified version of io.Copy.
|
|
||||||
//
|
|
||||||
// StdCopy will demultiplex `src`, assuming that it contains two streams,
|
|
||||||
// previously multiplexed together using a StdWriter instance.
|
|
||||||
// As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
|
|
||||||
//
|
|
||||||
// StdCopy will read until it hits EOF on `src`. It will then return a nil error.
|
|
||||||
// In other words: if `err` is non nil, it indicates a real underlying error.
|
|
||||||
//
|
|
||||||
// `written` will hold the total number of bytes written to `dstout` and `dsterr`.
|
|
||||||
func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
|
|
||||||
var (
|
|
||||||
buf = make([]byte, startingBufLen)
|
|
||||||
bufLen = len(buf)
|
|
||||||
nr, nw int
|
|
||||||
er, ew error
|
|
||||||
out io.Writer
|
|
||||||
frameSize int
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Make sure we have at least a full header
|
|
||||||
for nr < stdWriterPrefixLen {
|
|
||||||
var nr2 int
|
|
||||||
nr2, er = src.Read(buf[nr:])
|
|
||||||
nr += nr2
|
|
||||||
if er == io.EOF {
|
|
||||||
if nr < stdWriterPrefixLen {
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if er != nil {
|
|
||||||
return 0, er
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the first byte to know where to write
|
|
||||||
switch StdType(buf[stdWriterFdIndex]) {
|
|
||||||
case Stdin:
|
|
||||||
fallthrough
|
|
||||||
case Stdout:
|
|
||||||
// Write on stdout
|
|
||||||
out = dstout
|
|
||||||
case Stderr:
|
|
||||||
// Write on stderr
|
|
||||||
out = dsterr
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("Unrecognized input header: %d", buf[stdWriterFdIndex])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieve the size of the frame
|
|
||||||
frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
|
|
||||||
|
|
||||||
// Check if the buffer is big enough to read the frame.
|
|
||||||
// Extend it if necessary.
|
|
||||||
if frameSize+stdWriterPrefixLen > bufLen {
|
|
||||||
buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
|
|
||||||
bufLen = len(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// While the amount of bytes read is less than the size of the frame + header, we keep reading
|
|
||||||
for nr < frameSize+stdWriterPrefixLen {
|
|
||||||
var nr2 int
|
|
||||||
nr2, er = src.Read(buf[nr:])
|
|
||||||
nr += nr2
|
|
||||||
if er == io.EOF {
|
|
||||||
if nr < frameSize+stdWriterPrefixLen {
|
|
||||||
return written, nil
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if er != nil {
|
|
||||||
return 0, er
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write the retrieved frame (without header)
|
|
||||||
nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
|
|
||||||
if ew != nil {
|
|
||||||
return 0, ew
|
|
||||||
}
|
|
||||||
// If the frame has not been fully written: error
|
|
||||||
if nw != frameSize {
|
|
||||||
return 0, io.ErrShortWrite
|
|
||||||
}
|
|
||||||
written += int64(nw)
|
|
||||||
|
|
||||||
// Move the rest of the buffer to the beginning
|
|
||||||
copy(buf, buf[frameSize+stdWriterPrefixLen:])
|
|
||||||
// Move the index
|
|
||||||
nr -= frameSize + stdWriterPrefixLen
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,260 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNewStdWriter(t *testing.T) {
|
|
||||||
writer := NewStdWriter(ioutil.Discard, Stdout)
|
|
||||||
if writer == nil {
|
|
||||||
t.Fatalf("NewStdWriter with an invalid StdType should not return nil.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteWithUnitializedStdWriter(t *testing.T) {
|
|
||||||
writer := stdWriter{
|
|
||||||
Writer: nil,
|
|
||||||
prefix: byte(Stdout),
|
|
||||||
}
|
|
||||||
n, err := writer.Write([]byte("Something here"))
|
|
||||||
if n != 0 || err == nil {
|
|
||||||
t.Fatalf("Should fail when given an uncomplete or uninitialized StdWriter")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteWithNilBytes(t *testing.T) {
|
|
||||||
writer := NewStdWriter(ioutil.Discard, Stdout)
|
|
||||||
n, err := writer.Write(nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Shouldn't have fail when given no data")
|
|
||||||
}
|
|
||||||
if n > 0 {
|
|
||||||
t.Fatalf("Write should have written 0 byte, but has written %d", n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWrite(t *testing.T) {
|
|
||||||
writer := NewStdWriter(ioutil.Discard, Stdout)
|
|
||||||
data := []byte("Test StdWrite.Write")
|
|
||||||
n, err := writer.Write(data)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error while writing with StdWrite")
|
|
||||||
}
|
|
||||||
if n != len(data) {
|
|
||||||
t.Fatalf("Write should have written %d byte but wrote %d.", len(data), n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type errWriter struct {
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *errWriter) Write(buf []byte) (int, error) {
|
|
||||||
return f.n, f.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteWithWriterError(t *testing.T) {
|
|
||||||
expectedError := errors.New("expected")
|
|
||||||
expectedReturnedBytes := 10
|
|
||||||
writer := NewStdWriter(&errWriter{
|
|
||||||
n: stdWriterPrefixLen + expectedReturnedBytes,
|
|
||||||
err: expectedError}, Stdout)
|
|
||||||
data := []byte("This won't get written, sigh")
|
|
||||||
n, err := writer.Write(data)
|
|
||||||
if err != expectedError {
|
|
||||||
t.Fatalf("Didn't get expected error.")
|
|
||||||
}
|
|
||||||
if n != expectedReturnedBytes {
|
|
||||||
t.Fatalf("Didn't get expected written bytes %d, got %d.",
|
|
||||||
expectedReturnedBytes, n)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestWriteDoesNotReturnNegativeWrittenBytes(t *testing.T) {
|
|
||||||
writer := NewStdWriter(&errWriter{n: -1}, Stdout)
|
|
||||||
data := []byte("This won't get written, sigh")
|
|
||||||
actual, _ := writer.Write(data)
|
|
||||||
if actual != 0 {
|
|
||||||
t.Fatalf("Expected returned written bytes equal to 0, got %d", actual)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSrcBuffer(stdOutBytes, stdErrBytes []byte) (buffer *bytes.Buffer, err error) {
|
|
||||||
buffer = new(bytes.Buffer)
|
|
||||||
dstOut := NewStdWriter(buffer, Stdout)
|
|
||||||
_, err = dstOut.Write(stdOutBytes)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
dstErr := NewStdWriter(buffer, Stderr)
|
|
||||||
_, err = dstErr.Write(stdErrBytes)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyWriteAndRead(t *testing.T) {
|
|
||||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
|
||||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
|
||||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, buffer)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
expectedTotalWritten := len(stdOutBytes) + len(stdErrBytes)
|
|
||||||
if written != int64(expectedTotalWritten) {
|
|
||||||
t.Fatalf("Expected to have total of %d bytes written, got %d", expectedTotalWritten, written)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type customReader struct {
|
|
||||||
n int
|
|
||||||
err error
|
|
||||||
totalCalls int
|
|
||||||
correctCalls int
|
|
||||||
src *bytes.Buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *customReader) Read(buf []byte) (int, error) {
|
|
||||||
f.totalCalls++
|
|
||||||
if f.totalCalls <= f.correctCalls {
|
|
||||||
return f.src.Read(buf)
|
|
||||||
}
|
|
||||||
return f.n, f.err
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyReturnsErrorReadingHeader(t *testing.T) {
|
|
||||||
expectedError := errors.New("error")
|
|
||||||
reader := &customReader{
|
|
||||||
err: expectedError}
|
|
||||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader)
|
|
||||||
if written != 0 {
|
|
||||||
t.Fatalf("Expected 0 bytes read, got %d", written)
|
|
||||||
}
|
|
||||||
if err != expectedError {
|
|
||||||
t.Fatalf("Didn't get expected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyReturnsErrorReadingFrame(t *testing.T) {
|
|
||||||
expectedError := errors.New("error")
|
|
||||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
|
||||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
|
||||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
reader := &customReader{
|
|
||||||
correctCalls: 1,
|
|
||||||
n: stdWriterPrefixLen + 1,
|
|
||||||
err: expectedError,
|
|
||||||
src: buffer}
|
|
||||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader)
|
|
||||||
if written != 0 {
|
|
||||||
t.Fatalf("Expected 0 bytes read, got %d", written)
|
|
||||||
}
|
|
||||||
if err != expectedError {
|
|
||||||
t.Fatalf("Didn't get expected error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyDetectsCorruptedFrame(t *testing.T) {
|
|
||||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
|
||||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
|
||||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
reader := &customReader{
|
|
||||||
correctCalls: 1,
|
|
||||||
n: stdWriterPrefixLen + 1,
|
|
||||||
err: io.EOF,
|
|
||||||
src: buffer}
|
|
||||||
written, err := StdCopy(ioutil.Discard, ioutil.Discard, reader)
|
|
||||||
if written != startingBufLen {
|
|
||||||
t.Fatalf("Expected %d bytes read, got %d", startingBufLen, written)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal("Didn't get nil error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyWithInvalidInputHeader(t *testing.T) {
|
|
||||||
dstOut := NewStdWriter(ioutil.Discard, Stdout)
|
|
||||||
dstErr := NewStdWriter(ioutil.Discard, Stderr)
|
|
||||||
src := strings.NewReader("Invalid input")
|
|
||||||
_, err := StdCopy(dstOut, dstErr, src)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("StdCopy with invalid input header should fail.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyWithCorruptedPrefix(t *testing.T) {
|
|
||||||
data := []byte{0x01, 0x02, 0x03}
|
|
||||||
src := bytes.NewReader(data)
|
|
||||||
written, err := StdCopy(nil, nil, src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("StdCopy should not return an error with corrupted prefix.")
|
|
||||||
}
|
|
||||||
if written != 0 {
|
|
||||||
t.Fatalf("StdCopy should have written 0, but has written %d", written)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyReturnsWriteErrors(t *testing.T) {
|
|
||||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
|
||||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
|
||||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
expectedError := errors.New("expected")
|
|
||||||
|
|
||||||
dstOut := &errWriter{err: expectedError}
|
|
||||||
|
|
||||||
written, err := StdCopy(dstOut, ioutil.Discard, buffer)
|
|
||||||
if written != 0 {
|
|
||||||
t.Fatalf("StdCopy should have written 0, but has written %d", written)
|
|
||||||
}
|
|
||||||
if err != expectedError {
|
|
||||||
t.Fatalf("Didn't get expected error, got %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStdCopyDetectsNotFullyWrittenFrames(t *testing.T) {
|
|
||||||
stdOutBytes := []byte(strings.Repeat("o", startingBufLen))
|
|
||||||
stdErrBytes := []byte(strings.Repeat("e", startingBufLen))
|
|
||||||
buffer, err := getSrcBuffer(stdOutBytes, stdErrBytes)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
dstOut := &errWriter{n: startingBufLen - 10}
|
|
||||||
|
|
||||||
written, err := StdCopy(dstOut, ioutil.Discard, buffer)
|
|
||||||
if written != 0 {
|
|
||||||
t.Fatalf("StdCopy should have return 0 written bytes, but returned %d", written)
|
|
||||||
}
|
|
||||||
if err != io.ErrShortWrite {
|
|
||||||
t.Fatalf("Didn't get expected io.ErrShortWrite error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkWrite(b *testing.B) {
|
|
||||||
w := NewStdWriter(ioutil.Discard, Stdout)
|
|
||||||
data := []byte("Test line for testing stdwriter performance\n")
|
|
||||||
data = bytes.Repeat(data, 100)
|
|
||||||
b.SetBytes(int64(len(data)))
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if _, err := w.Write(data); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,102 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/drone/drone/yaml"
|
|
||||||
"github.com/samalba/dockerclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
// helper function that converts the Continer data structure to the exepcted
|
|
||||||
// dockerclient.ContainerConfig.
|
|
||||||
func toContainerConfig(c *yaml.Container) *dockerclient.ContainerConfig {
|
|
||||||
config := &dockerclient.ContainerConfig{
|
|
||||||
Image: c.Image,
|
|
||||||
Env: toEnvironmentSlice(c.Environment),
|
|
||||||
Labels: c.Labels,
|
|
||||||
Cmd: c.Command,
|
|
||||||
Entrypoint: c.Entrypoint,
|
|
||||||
WorkingDir: c.WorkingDir,
|
|
||||||
HostConfig: dockerclient.HostConfig{
|
|
||||||
Privileged: c.Privileged,
|
|
||||||
NetworkMode: c.Network,
|
|
||||||
Memory: c.MemLimit,
|
|
||||||
ShmSize: c.ShmSize,
|
|
||||||
CpuShares: c.CPUShares,
|
|
||||||
CpuQuota: c.CPUQuota,
|
|
||||||
CpusetCpus: c.CPUSet,
|
|
||||||
MemorySwappiness: -1,
|
|
||||||
OomKillDisable: c.OomKillDisable,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(config.Entrypoint) == 0 {
|
|
||||||
config.Entrypoint = nil
|
|
||||||
}
|
|
||||||
if len(config.Cmd) == 0 {
|
|
||||||
config.Cmd = nil
|
|
||||||
}
|
|
||||||
if len(c.ExtraHosts) > 0 {
|
|
||||||
config.HostConfig.ExtraHosts = c.ExtraHosts
|
|
||||||
}
|
|
||||||
if len(c.DNS) != 0 {
|
|
||||||
config.HostConfig.Dns = c.DNS
|
|
||||||
}
|
|
||||||
if len(c.DNSSearch) != 0 {
|
|
||||||
config.HostConfig.DnsSearch = c.DNSSearch
|
|
||||||
}
|
|
||||||
if len(c.VolumesFrom) != 0 {
|
|
||||||
config.HostConfig.VolumesFrom = c.VolumesFrom
|
|
||||||
}
|
|
||||||
|
|
||||||
config.Volumes = map[string]struct{}{}
|
|
||||||
for _, path := range c.Volumes {
|
|
||||||
if strings.Index(path, ":") == -1 {
|
|
||||||
config.Volumes[path] = struct{}{}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Split(path, ":")
|
|
||||||
config.Volumes[parts[1]] = struct{}{}
|
|
||||||
config.HostConfig.Binds = append(config.HostConfig.Binds, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, path := range c.Devices {
|
|
||||||
if strings.Index(path, ":") == -1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.Split(path, ":")
|
|
||||||
device := dockerclient.DeviceMapping{
|
|
||||||
PathOnHost: parts[0],
|
|
||||||
PathInContainer: parts[1],
|
|
||||||
CgroupPermissions: "rwm",
|
|
||||||
}
|
|
||||||
config.HostConfig.Devices = append(config.HostConfig.Devices, device)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function that converts the AuthConfig data structure to the exepcted
|
|
||||||
// dockerclient.AuthConfig.
|
|
||||||
func toAuthConfig(container *yaml.Container) *dockerclient.AuthConfig {
|
|
||||||
if container.AuthConfig.Username == "" &&
|
|
||||||
container.AuthConfig.Password == "" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &dockerclient.AuthConfig{
|
|
||||||
Email: container.AuthConfig.Email,
|
|
||||||
Username: container.AuthConfig.Username,
|
|
||||||
Password: container.AuthConfig.Password,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// helper function that converts a key value map of environment variables to a
|
|
||||||
// string slice in key=value format.
|
|
||||||
func toEnvironmentSlice(env map[string]string) []string {
|
|
||||||
var envs []string
|
|
||||||
for k, v := range env {
|
|
||||||
envs = append(envs, fmt.Sprintf("%s=%s", k, v))
|
|
||||||
}
|
|
||||||
return envs
|
|
||||||
}
|
|
@ -1,24 +0,0 @@
|
|||||||
package docker
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Test_toContainerConfig(t *testing.T) {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_toAuthConfig(t *testing.T) {
|
|
||||||
t.Skip()
|
|
||||||
}
|
|
||||||
|
|
||||||
func Test_toEnvironmentSlice(t *testing.T) {
|
|
||||||
env := map[string]string{
|
|
||||||
"HOME": "/root",
|
|
||||||
}
|
|
||||||
envs := toEnvironmentSlice(env)
|
|
||||||
want, got := "HOME=/root", envs[0]
|
|
||||||
if want != got {
|
|
||||||
t.Errorf("Wanted envar %s got %s", want, got)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,16 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"github.com/drone/drone/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Engine defines the container runtime engine.
|
|
||||||
type Engine interface {
|
|
||||||
ContainerStart(*yaml.Container) (string, error)
|
|
||||||
ContainerStop(string) error
|
|
||||||
ContainerRemove(string) error
|
|
||||||
ContainerWait(string) (*State, error)
|
|
||||||
ContainerLogs(string) (io.ReadCloser, error)
|
|
||||||
}
|
|
@ -1,37 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrSkip is used as a return value when container execution should be
|
|
||||||
// skipped at runtime. It is not returned as an error by any function.
|
|
||||||
ErrSkip = errors.New("Skip")
|
|
||||||
|
|
||||||
// ErrTerm is used as a return value when the runner should terminate
|
|
||||||
// execution and exit. It is not returned as an error by any function.
|
|
||||||
ErrTerm = errors.New("Terminate")
|
|
||||||
)
|
|
||||||
|
|
||||||
// An ExitError reports an unsuccessful exit.
|
|
||||||
type ExitError struct {
|
|
||||||
Name string
|
|
||||||
Code int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the error message in string format.
|
|
||||||
func (e *ExitError) Error() string {
|
|
||||||
return fmt.Sprintf("%s : exit code %d", e.Name, e.Code)
|
|
||||||
}
|
|
||||||
|
|
||||||
// An OomError reports the process received an OOMKill from the kernel.
|
|
||||||
type OomError struct {
|
|
||||||
Name string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns the error message in string format.
|
|
||||||
func (e *OomError) Error() string {
|
|
||||||
return fmt.Sprintf("%s : received oom kill", e.Name)
|
|
||||||
}
|
|
@ -1,26 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/franela/goblin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestErrors(t *testing.T) {
|
|
||||||
g := goblin.Goblin(t)
|
|
||||||
|
|
||||||
g.Describe("Error messages", func() {
|
|
||||||
|
|
||||||
g.It("should include OOM details", func() {
|
|
||||||
err := OomError{Name: "golang"}
|
|
||||||
got, want := err.Error(), "golang : received oom kill"
|
|
||||||
g.Assert(got).Equal(want)
|
|
||||||
})
|
|
||||||
|
|
||||||
g.It("should include Exit code", func() {
|
|
||||||
err := ExitError{Name: "golang", Code: 255}
|
|
||||||
got, want := err.Error(), "golang : exit code 255"
|
|
||||||
g.Assert(got).Equal(want)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,241 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/yaml"
|
|
||||||
)
|
|
||||||
|
|
||||||
// element represents a link in the linked list.
|
|
||||||
type element struct {
|
|
||||||
*yaml.Container
|
|
||||||
next *element
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipeline represents a build pipeline.
|
|
||||||
type Pipeline struct {
|
|
||||||
conf *yaml.Config
|
|
||||||
head *element
|
|
||||||
tail *element
|
|
||||||
wait sync.WaitGroup
|
|
||||||
pipe chan (*Line)
|
|
||||||
next chan (error)
|
|
||||||
done chan (error)
|
|
||||||
err error
|
|
||||||
|
|
||||||
containers []string
|
|
||||||
volumes []string
|
|
||||||
networks []string
|
|
||||||
|
|
||||||
engine Engine
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done returns when the process is done executing.
|
|
||||||
func (p *Pipeline) Done() <-chan error {
|
|
||||||
return p.done
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns the error for the current process.
|
|
||||||
func (p *Pipeline) Err() error {
|
|
||||||
return p.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next returns the next step in the process.
|
|
||||||
func (p *Pipeline) Next() <-chan error {
|
|
||||||
return p.next
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec executes the current step.
|
|
||||||
func (p *Pipeline) Exec() {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logrus.Errorln("recover executing build step", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
err := p.exec(p.head.Container)
|
|
||||||
if err != nil {
|
|
||||||
p.err = err
|
|
||||||
}
|
|
||||||
p.step()
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip skips the current step.
|
|
||||||
func (p *Pipeline) Skip() {
|
|
||||||
p.step()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipe returns the build output pipe.
|
|
||||||
func (p *Pipeline) Pipe() <-chan *Line {
|
|
||||||
return p.pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// Head returns the head item in the list.
|
|
||||||
func (p *Pipeline) Head() *yaml.Container {
|
|
||||||
return p.head.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tail returns the tail item in the list.
|
|
||||||
func (p *Pipeline) Tail() *yaml.Container {
|
|
||||||
return p.tail.Container
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the pipeline.
|
|
||||||
func (p *Pipeline) Stop() {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logrus.Errorln("recover stopping the pipeline", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
p.done <- ErrTerm
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup prepares the build pipeline environment.
|
|
||||||
func (p *Pipeline) Setup() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Teardown removes the pipeline environment.
|
|
||||||
func (p *Pipeline) Teardown() {
|
|
||||||
|
|
||||||
for _, id := range p.containers {
|
|
||||||
p.engine.ContainerRemove(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
close(p.next)
|
|
||||||
close(p.done)
|
|
||||||
|
|
||||||
// TODO we have a race condition here where the program can try to async
|
|
||||||
// write to a closed pipe channel. This package, in general, needs to be
|
|
||||||
// tested for race conditions.
|
|
||||||
// close(p.pipe)
|
|
||||||
}
|
|
||||||
|
|
||||||
// step steps through the pipeline to head.next
|
|
||||||
func (p *Pipeline) step() {
|
|
||||||
if p.head == p.tail {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logrus.Errorln("recover executing step function", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// stop all containers
|
|
||||||
for _, id := range p.containers {
|
|
||||||
p.engine.ContainerStop(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// wait for all logs to terminate
|
|
||||||
// p.wait.Done() // this is for the ambassador
|
|
||||||
p.wait.Wait()
|
|
||||||
|
|
||||||
// signal completion
|
|
||||||
p.done <- nil
|
|
||||||
}()
|
|
||||||
} else {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logrus.Errorln("recover executing step to head function", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
p.head = p.head.next
|
|
||||||
p.next <- nil
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// close closes open channels and signals the pipeline is done.
|
|
||||||
func (p *Pipeline) close(err error) {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logrus.Errorln("recover closing the pipeline", r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
p.done <- err
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pipeline) exec(c *yaml.Container) error {
|
|
||||||
|
|
||||||
name, err := p.engine.ContainerStart(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
p.containers = append(p.containers, name)
|
|
||||||
|
|
||||||
p.wait.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logrus.Errorln("recover writing build output", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.wait.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
rc, rerr := p.engine.ContainerLogs(name)
|
|
||||||
if rerr != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer rc.Close()
|
|
||||||
|
|
||||||
num := 0
|
|
||||||
now := time.Now().UTC()
|
|
||||||
scanner := bufio.NewScanner(rc)
|
|
||||||
for scanner.Scan() {
|
|
||||||
p.pipe <- &Line{
|
|
||||||
Proc: c.Name,
|
|
||||||
Time: int64(time.Since(now).Seconds()),
|
|
||||||
Pos: num,
|
|
||||||
Out: scanner.Text(),
|
|
||||||
}
|
|
||||||
num++
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
// exit when running container in detached mode in background
|
|
||||||
if c.Detached {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
state, err := p.engine.ContainerWait(name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.wait.Add(1)
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
logrus.Errorln("recover writing exit code to output", r)
|
|
||||||
}
|
|
||||||
p.wait.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
p.pipe <- &Line{
|
|
||||||
Proc: c.Name,
|
|
||||||
Type: ExitCodeLine,
|
|
||||||
Out: strconv.Itoa(state.ExitCode),
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if state.OOMKilled {
|
|
||||||
return &OomError{c.Name}
|
|
||||||
} else if state.ExitCode != 0 {
|
|
||||||
return &ExitError{c.Name, state.ExitCode}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,42 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
var sampleYaml = `
|
|
||||||
image: hello-world
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
|
|
||||||
workspace:
|
|
||||||
path: src/github.com/octocat/hello-world
|
|
||||||
base: /go
|
|
||||||
|
|
||||||
pipeline:
|
|
||||||
test:
|
|
||||||
image: golang
|
|
||||||
commands:
|
|
||||||
- go install
|
|
||||||
- go test
|
|
||||||
build:
|
|
||||||
image: golang
|
|
||||||
commands:
|
|
||||||
- go build
|
|
||||||
when:
|
|
||||||
event: push
|
|
||||||
notify:
|
|
||||||
image: slack
|
|
||||||
channel: dev
|
|
||||||
when:
|
|
||||||
event: failure
|
|
||||||
|
|
||||||
services:
|
|
||||||
database:
|
|
||||||
image: mysql
|
|
||||||
|
|
||||||
networks:
|
|
||||||
custom:
|
|
||||||
driver: overlay
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
custom:
|
|
||||||
driver: blockbridge
|
|
||||||
`
|
|
@ -1,35 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
StdoutLine int = iota
|
|
||||||
StderrLine
|
|
||||||
ExitCodeLine
|
|
||||||
MetadataLine
|
|
||||||
ProgressLine
|
|
||||||
)
|
|
||||||
|
|
||||||
// Line is a line of console output.
|
|
||||||
type Line struct {
|
|
||||||
Proc string `json:"proc,omitempty"`
|
|
||||||
Time int64 `json:"time,omitempty"`
|
|
||||||
Type int `json:"type,omitempty"`
|
|
||||||
Pos int `json:"pos,omityempty"`
|
|
||||||
Out string `json:"out,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *Line) String() string {
|
|
||||||
switch l.Type {
|
|
||||||
case ExitCodeLine:
|
|
||||||
return fmt.Sprintf("[%s] exit code %s", l.Proc, l.Out)
|
|
||||||
default:
|
|
||||||
return fmt.Sprintf("[%s:L%v:%vs] %s", l.Proc, l.Pos, l.Time, l.Out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// State defines the state of the container.
|
|
||||||
type State struct {
|
|
||||||
ExitCode int // container exit code
|
|
||||||
OOMKilled bool // container exited due to oom error
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
package build
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/franela/goblin"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLine(t *testing.T) {
|
|
||||||
g := goblin.Goblin(t)
|
|
||||||
|
|
||||||
g.Describe("Line output", func() {
|
|
||||||
g.It("should prefix string() with metadata", func() {
|
|
||||||
line := Line{
|
|
||||||
Proc: "redis",
|
|
||||||
Time: 60,
|
|
||||||
Pos: 1,
|
|
||||||
Out: "starting redis server",
|
|
||||||
}
|
|
||||||
g.Assert(line.String()).Equal("[redis:L1:60s] starting redis server")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
@ -1,77 +1,37 @@
|
|||||||
package agent
|
package agent
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"os"
|
"net/url"
|
||||||
"os/signal"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/cncd/pipeline/pipeline"
|
||||||
"github.com/drone/mq/logger"
|
"github.com/cncd/pipeline/pipeline/backend"
|
||||||
"github.com/drone/mq/stomp"
|
"github.com/cncd/pipeline/pipeline/backend/docker"
|
||||||
"github.com/tidwall/redlog"
|
"github.com/cncd/pipeline/pipeline/interrupt"
|
||||||
|
"github.com/cncd/pipeline/pipeline/multipart"
|
||||||
|
"github.com/cncd/pipeline/pipeline/rpc"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/tevino/abool"
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
"github.com/samalba/dockerclient"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// AgentCmd is the exported command for starting the drone agent.
|
// AgentCmd is the exported command for starting the drone agent.
|
||||||
var AgentCmd = cli.Command{
|
var AgentCmd = cli.Command{
|
||||||
Name: "agent",
|
Name: "agent",
|
||||||
Usage: "starts the drone agent",
|
Usage: "starts the drone agent",
|
||||||
Action: start,
|
Action: loop,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DOCKER_HOST",
|
|
||||||
Name: "docker-host",
|
|
||||||
Usage: "docker daemon address",
|
|
||||||
Value: "unix:///var/run/docker.sock",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
EnvVar: "DOCKER_TLS_VERIFY",
|
|
||||||
Name: "docker-tls-verify",
|
|
||||||
Usage: "docker daemon supports tlsverify",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DOCKER_CERT_PATH",
|
|
||||||
Name: "docker-cert-path",
|
|
||||||
Usage: "docker certificate directory",
|
|
||||||
Value: "",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
EnvVar: "DOCKER_MAX_PROCS",
|
|
||||||
Name: "docker-max-procs",
|
|
||||||
Usage: "limit number of running docker processes",
|
|
||||||
Value: 2,
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DOCKER_OS",
|
|
||||||
Name: "docker-os",
|
|
||||||
Usage: "docker operating system",
|
|
||||||
Value: "linux",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DOCKER_ARCH",
|
|
||||||
Name: "docker-arch",
|
|
||||||
Usage: "docker architecture system",
|
|
||||||
Value: "amd64",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
EnvVar: "DRONE_SERVER,DRONE_ENDPOINT",
|
EnvVar: "DRONE_SERVER,DRONE_ENDPOINT",
|
||||||
Name: "drone-server",
|
Name: "drone-server",
|
||||||
Usage: "drone server address",
|
Usage: "drone server address",
|
||||||
Value: "ws://localhost:8000/ws/broker",
|
Value: "ws://localhost:8000/ws/broker",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DRONE_TOKEN",
|
|
||||||
Name: "drone-token",
|
|
||||||
Usage: "drone authorization token",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
EnvVar: "DRONE_SECRET,DRONE_AGENT_SECRET",
|
EnvVar: "DRONE_SECRET,DRONE_AGENT_SECRET",
|
||||||
Name: "drone-secret",
|
Name: "drone-secret",
|
||||||
@ -83,89 +43,21 @@ var AgentCmd = cli.Command{
|
|||||||
Usage: "drone server backoff interval",
|
Usage: "drone server backoff interval",
|
||||||
Value: time.Second * 15,
|
Value: time.Second * 15,
|
||||||
},
|
},
|
||||||
cli.DurationFlag{
|
cli.IntFlag{
|
||||||
EnvVar: "DRONE_PING",
|
Name: "retry-limit",
|
||||||
Name: "ping",
|
EnvVar: "DRONE_RETRY_LIMIT",
|
||||||
Usage: "drone server ping frequency",
|
Value: math.MaxInt32,
|
||||||
Value: time.Minute * 5,
|
|
||||||
},
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
EnvVar: "DRONE_DEBUG",
|
EnvVar: "DRONE_DEBUG",
|
||||||
Name: "debug",
|
Name: "debug",
|
||||||
Usage: "start the agent in debug mode",
|
Usage: "start the agent in debug mode",
|
||||||
},
|
},
|
||||||
cli.DurationFlag{
|
|
||||||
EnvVar: "DRONE_TIMEOUT",
|
|
||||||
Name: "timeout",
|
|
||||||
Usage: "drone timeout due to log inactivity",
|
|
||||||
Value: time.Minute * 15,
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
EnvVar: "DRONE_FILTER",
|
EnvVar: "DRONE_FILTER",
|
||||||
Name: "filter",
|
Name: "filter",
|
||||||
Usage: "filter jobs processed by this agent",
|
Usage: "filter jobs processed by this agent",
|
||||||
},
|
},
|
||||||
cli.IntFlag{
|
|
||||||
EnvVar: "DRONE_MAX_LOGS",
|
|
||||||
Name: "max-log-size",
|
|
||||||
Usage: "drone maximum log size in megabytes",
|
|
||||||
Value: 5,
|
|
||||||
},
|
|
||||||
cli.StringSliceFlag{
|
|
||||||
EnvVar: "DRONE_PLUGIN_PRIVILEGED",
|
|
||||||
Name: "privileged",
|
|
||||||
Usage: "plugins that require privileged mode",
|
|
||||||
Value: &cli.StringSlice{
|
|
||||||
"plugins/docker",
|
|
||||||
"plugins/docker:*",
|
|
||||||
"plugins/gcr",
|
|
||||||
"plugins/gcr:*",
|
|
||||||
"plugins/ecr",
|
|
||||||
"plugins/ecr:*",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DRONE_PLUGIN_NAMESPACE",
|
|
||||||
Name: "namespace",
|
|
||||||
Value: "plugins",
|
|
||||||
Usage: "default plugin image namespace",
|
|
||||||
},
|
|
||||||
cli.BoolTFlag{
|
|
||||||
EnvVar: "DRONE_PLUGIN_PULL",
|
|
||||||
Name: "pull",
|
|
||||||
Usage: "always pull latest plugin images",
|
|
||||||
},
|
|
||||||
cli.StringSliceFlag{
|
|
||||||
EnvVar: "DRONE_YAML_EXTENSION",
|
|
||||||
Name: "extension",
|
|
||||||
Usage: "custom plugin extension endpoint",
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
cli.BoolFlag{
|
|
||||||
EnvVar: "DRONE_CANARY",
|
|
||||||
Name: "canary",
|
|
||||||
Usage: "enable experimental features at your own risk",
|
|
||||||
},
|
|
||||||
|
|
||||||
// cli.StringFlag{
|
|
||||||
// Name: "endpoint",
|
|
||||||
// EnvVar: "DRONE_ENDPOINT,DRONE_SERVER",
|
|
||||||
// Value: "ws://localhost:9999/ws/rpc",
|
|
||||||
// },
|
|
||||||
// cli.DurationFlag{
|
|
||||||
// Name: "backoff",
|
|
||||||
// EnvVar: "DRONE_BACKOFF",
|
|
||||||
// Value: time.Second * 15,
|
|
||||||
// },
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "retry-limit",
|
|
||||||
EnvVar: "DRONE_RETRY_LIMIT",
|
|
||||||
Value: math.MaxInt32,
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
cli.IntFlag{
|
||||||
Name: "max-procs",
|
Name: "max-procs",
|
||||||
EnvVar: "DRONE_MAX_PROCS",
|
EnvVar: "DRONE_MAX_PROCS",
|
||||||
@ -179,139 +71,188 @@ var AgentCmd = cli.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(c *cli.Context) {
|
func loop(c *cli.Context) error {
|
||||||
|
endpoint, err := url.Parse(
|
||||||
if c.Bool("canary") {
|
c.String("drone-server"),
|
||||||
if err := loop(c); err != nil {
|
)
|
||||||
fmt.Println(err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log := redlog.New(os.Stderr)
|
|
||||||
log.SetLevel(0)
|
|
||||||
logger.SetLogger(log)
|
|
||||||
|
|
||||||
// debug level if requested by user
|
|
||||||
if c.Bool("debug") {
|
|
||||||
logrus.SetLevel(logrus.DebugLevel)
|
|
||||||
|
|
||||||
log.SetLevel(1)
|
|
||||||
} else {
|
|
||||||
logrus.SetLevel(logrus.WarnLevel)
|
|
||||||
}
|
|
||||||
|
|
||||||
var accessToken string
|
|
||||||
if c.String("drone-secret") != "" {
|
|
||||||
// secretToken := c.String("drone-secret")
|
|
||||||
accessToken = c.String("drone-secret")
|
|
||||||
// accessToken, _ = token.New(token.AgentToken, "").Sign(secretToken)
|
|
||||||
} else {
|
|
||||||
accessToken = c.String("drone-token")
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.Noticef("connecting to server %s", c.String("drone-server"))
|
|
||||||
|
|
||||||
server := strings.TrimRight(c.String("drone-server"), "/")
|
|
||||||
|
|
||||||
tls, err := dockerclient.TLSConfigFromCertPath(c.String("docker-cert-path"))
|
|
||||||
if err == nil {
|
|
||||||
tls.InsecureSkipVerify = c.Bool("docker-tls-verify")
|
|
||||||
}
|
|
||||||
docker, err := dockerclient.NewDockerClient(c.String("docker-host"), tls)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logrus.Fatal(err)
|
return err
|
||||||
|
}
|
||||||
|
filter := rpc.Filter{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"platform": c.String("platform"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var client *stomp.Client
|
client, err := rpc.NewClient(
|
||||||
|
endpoint.String(),
|
||||||
|
rpc.WithRetryLimit(
|
||||||
|
c.Int("retry-limit"),
|
||||||
|
),
|
||||||
|
rpc.WithBackoff(
|
||||||
|
c.Duration("backoff"),
|
||||||
|
),
|
||||||
|
rpc.WithToken(
|
||||||
|
c.String("drone-secret"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer client.Close()
|
||||||
|
|
||||||
handler := func(m *stomp.Message) {
|
sigterm := abool.New()
|
||||||
running.Add(1)
|
ctx := context.Background()
|
||||||
defer func() {
|
ctx = interrupt.WithContextFunc(ctx, func() {
|
||||||
running.Done()
|
println("ctrl+c received, terminating process")
|
||||||
client.Ack(m.Ack)
|
sigterm.Set()
|
||||||
|
})
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
parallel := c.Int("max-procs")
|
||||||
|
wg.Add(parallel)
|
||||||
|
|
||||||
|
for i := 0; i < parallel; i++ {
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
for {
|
||||||
|
if sigterm.IsSet() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := run(ctx, client, filter); err != nil {
|
||||||
|
log.Printf("build runner encountered error: exiting: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
r := pipelinet{
|
|
||||||
drone: client,
|
|
||||||
docker: docker,
|
|
||||||
config: config{
|
|
||||||
platform: c.String("docker-os") + "/" + c.String("docker-arch"),
|
|
||||||
timeout: c.Duration("timeout"),
|
|
||||||
namespace: c.String("namespace"),
|
|
||||||
privileged: c.StringSlice("privileged"),
|
|
||||||
pull: c.BoolT("pull"),
|
|
||||||
logs: int64(c.Int("max-log-size")) * 1000000,
|
|
||||||
extension: c.StringSlice("extension"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
work := new(model.Work)
|
|
||||||
m.Unmarshal(work)
|
|
||||||
r.run(work)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSignals()
|
wg.Wait()
|
||||||
|
return nil
|
||||||
backoff := c.Duration("backoff")
|
|
||||||
|
|
||||||
for {
|
|
||||||
// dial the drone server to establish a TCP connection.
|
|
||||||
client, err = stomp.Dial(server)
|
|
||||||
if err != nil {
|
|
||||||
logger.Warningf("connection failed, retry in %v. %s", backoff, err)
|
|
||||||
<-time.After(backoff)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
opts := []stomp.MessageOption{
|
|
||||||
stomp.WithCredentials("x-token", accessToken),
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize the stomp session and authenticate.
|
|
||||||
if err = client.Connect(opts...); err != nil {
|
|
||||||
logger.Warningf("session failed, retry in %v. %s", backoff, err)
|
|
||||||
<-time.After(backoff)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
opts = []stomp.MessageOption{
|
|
||||||
stomp.WithAck("client"),
|
|
||||||
stomp.WithPrefetch(
|
|
||||||
c.Int("docker-max-procs"),
|
|
||||||
),
|
|
||||||
}
|
|
||||||
if filter := c.String("filter"); filter != "" {
|
|
||||||
opts = append(opts, stomp.WithSelector(filter))
|
|
||||||
}
|
|
||||||
|
|
||||||
// subscribe to the pending build queue.
|
|
||||||
client.Subscribe("/queue/pending", stomp.HandlerFunc(func(m *stomp.Message) {
|
|
||||||
go handler(m) // HACK until we a channel based Subscribe implementation
|
|
||||||
}), opts...)
|
|
||||||
|
|
||||||
logger.Noticef("connection established, ready to process builds.")
|
|
||||||
<-client.Done()
|
|
||||||
|
|
||||||
logger.Warningf("connection interrupted, attempting to reconnect.")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// tracks running builds
|
const (
|
||||||
var running sync.WaitGroup
|
maxFileUpload = 5000000
|
||||||
|
maxLogsUpload = 5000000
|
||||||
|
)
|
||||||
|
|
||||||
func handleSignals() {
|
func run(ctx context.Context, client rpc.Peer, filter rpc.Filter) error {
|
||||||
// Graceful shut-down on SIGINT/SIGTERM
|
log.Println("pipeline: request next execution")
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c, os.Interrupt)
|
// get the next job from the queue
|
||||||
signal.Notify(c, syscall.SIGTERM)
|
work, err := client.Next(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if work == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Printf("pipeline: received next execution: %s", work.ID)
|
||||||
|
|
||||||
|
// new docker engine
|
||||||
|
engine, err := docker.NewEnv()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout := time.Hour
|
||||||
|
if minutes := work.Timeout; minutes != 0 {
|
||||||
|
timeout = time.Duration(minutes) * time.Minute
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cancelled := abool.New()
|
||||||
|
go func() {
|
||||||
|
if werr := client.Wait(ctx, work.ID); werr != nil {
|
||||||
|
cancelled.SetTo(true)
|
||||||
|
log.Printf("pipeline: cancel signal received: %s: %s", work.ID, werr)
|
||||||
|
cancel()
|
||||||
|
} else {
|
||||||
|
log.Printf("pipeline: cancel channel closed: %s", work.ID)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-c
|
for {
|
||||||
logger.Warningf("SIGTERM received.")
|
select {
|
||||||
logger.Warningf("wait for running builds to finish.")
|
case <-ctx.Done():
|
||||||
running.Wait()
|
log.Printf("pipeline: cancel ping loop: %s", work.ID)
|
||||||
logger.Warningf("done.")
|
return
|
||||||
os.Exit(0)
|
case <-time.After(time.Minute):
|
||||||
|
log.Printf("pipeline: ping queue: %s", work.ID)
|
||||||
|
client.Extend(ctx, work.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
state := rpc.State{}
|
||||||
|
state.Started = time.Now().Unix()
|
||||||
|
err = client.Update(context.Background(), work.ID, state)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("pipeline: error updating pipeline status: %s: %s", work.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var uploads sync.WaitGroup
|
||||||
|
defaultLogger := pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
|
||||||
|
part, rerr := rc.NextPart()
|
||||||
|
if rerr != nil {
|
||||||
|
return rerr
|
||||||
|
}
|
||||||
|
uploads.Add(1)
|
||||||
|
writer := rpc.NewLineWriter(client, work.ID, proc.Alias)
|
||||||
|
rlimit := io.LimitReader(part, maxLogsUpload)
|
||||||
|
io.Copy(writer, rlimit)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
log.Printf("pipeline: finish uploading logs: %s: step %s", work.ID, proc.Alias)
|
||||||
|
uploads.Done()
|
||||||
|
}()
|
||||||
|
|
||||||
|
part, rerr = rc.NextPart()
|
||||||
|
if rerr != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
rlimit = io.LimitReader(part, maxFileUpload)
|
||||||
|
mime := part.Header().Get("Content-Type")
|
||||||
|
if serr := client.Upload(context.Background(), work.ID, mime, rlimit); serr != nil {
|
||||||
|
log.Printf("pipeline: cannot upload artifact: %s: %s: %s", work.ID, mime, serr)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
err = pipeline.New(work.Config,
|
||||||
|
pipeline.WithContext(ctx),
|
||||||
|
pipeline.WithLogger(defaultLogger),
|
||||||
|
pipeline.WithTracer(pipeline.DefaultTracer),
|
||||||
|
pipeline.WithEngine(engine),
|
||||||
|
).Run()
|
||||||
|
|
||||||
|
state.Finished = time.Now().Unix()
|
||||||
|
state.Exited = true
|
||||||
|
if err != nil {
|
||||||
|
state.Error = err.Error()
|
||||||
|
if xerr, ok := err.(*pipeline.ExitError); ok {
|
||||||
|
state.ExitCode = xerr.Code
|
||||||
|
}
|
||||||
|
if xerr, ok := err.(*pipeline.OomError); ok {
|
||||||
|
state.ExitCode = xerr.Code
|
||||||
|
}
|
||||||
|
if cancelled.IsSet() {
|
||||||
|
state.ExitCode = 137
|
||||||
|
} else if state.ExitCode == 0 {
|
||||||
|
state.ExitCode = 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("pipeline: execution complete: %s", work.ID)
|
||||||
|
|
||||||
|
uploads.Wait()
|
||||||
|
err = client.Update(context.Background(), work.ID, state)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Pipeine: error updating pipeline status: %s: %s", work.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,85 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/agent"
|
|
||||||
"github.com/drone/drone/build/docker"
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
|
|
||||||
"github.com/samalba/dockerclient"
|
|
||||||
)
|
|
||||||
|
|
||||||
type config struct {
|
|
||||||
platform string
|
|
||||||
namespace string
|
|
||||||
privileged []string
|
|
||||||
pull bool
|
|
||||||
logs int64
|
|
||||||
timeout time.Duration
|
|
||||||
extension []string
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipelinet struct {
|
|
||||||
drone *stomp.Client
|
|
||||||
docker dockerclient.Client
|
|
||||||
config config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *pipelinet) run(w *model.Work) {
|
|
||||||
|
|
||||||
// defer func() {
|
|
||||||
// // r.drone.Ack(id, opts)
|
|
||||||
// }()
|
|
||||||
|
|
||||||
logrus.Infof("Starting build %s/%s#%d.%d",
|
|
||||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
|
||||||
|
|
||||||
cancel := make(chan bool, 1)
|
|
||||||
engine := docker.NewClient(r.docker)
|
|
||||||
|
|
||||||
a := agent.Agent{
|
|
||||||
Update: agent.NewClientUpdater(r.drone),
|
|
||||||
Logger: agent.NewClientLogger(r.drone, w.Job.ID, r.config.logs),
|
|
||||||
Engine: engine,
|
|
||||||
Timeout: r.config.timeout,
|
|
||||||
Platform: r.config.platform,
|
|
||||||
Namespace: r.config.namespace,
|
|
||||||
Escalate: r.config.privileged,
|
|
||||||
Extension: r.config.extension,
|
|
||||||
Pull: r.config.pull,
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelFunc := func(m *stomp.Message) {
|
|
||||||
defer m.Release()
|
|
||||||
|
|
||||||
id := m.Header.GetInt64("job-id")
|
|
||||||
if id == w.Job.ID {
|
|
||||||
cancel <- true
|
|
||||||
logrus.Infof("Cancel build %s/%s#%d.%d",
|
|
||||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// signal for canceling the build.
|
|
||||||
sub, err := r.drone.Subscribe("/topic/cancel", stomp.HandlerFunc(cancelFunc))
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Error subscribing to /topic/cancel. %s", err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
r.drone.Unsubscribe(sub)
|
|
||||||
}()
|
|
||||||
|
|
||||||
a.Run(w, cancel)
|
|
||||||
|
|
||||||
// if err := r.drone.LogPost(w.Job.ID, ioutil.NopCloser(&buf)); err != nil {
|
|
||||||
// logrus.Errorf("Error sending logs for %s/%s#%d.%d",
|
|
||||||
// w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
|
||||||
// }
|
|
||||||
// stream.Close()
|
|
||||||
|
|
||||||
logrus.Infof("Finished build %s/%s#%d.%d",
|
|
||||||
w.Repo.Owner, w.Repo.Name, w.Build.Number, w.Job.Number)
|
|
||||||
}
|
|
@ -1,206 +0,0 @@
|
|||||||
package agent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/cncd/pipeline/pipeline"
|
|
||||||
"github.com/cncd/pipeline/pipeline/backend"
|
|
||||||
"github.com/cncd/pipeline/pipeline/backend/docker"
|
|
||||||
"github.com/cncd/pipeline/pipeline/interrupt"
|
|
||||||
"github.com/cncd/pipeline/pipeline/multipart"
|
|
||||||
"github.com/cncd/pipeline/pipeline/rpc"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/tevino/abool"
|
|
||||||
)
|
|
||||||
|
|
||||||
func loop(c *cli.Context) error {
|
|
||||||
endpoint, err := url.Parse(
|
|
||||||
c.String("drone-server"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
filter := rpc.Filter{
|
|
||||||
Labels: map[string]string{
|
|
||||||
"platform": c.String("platform"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := rpc.NewClient(
|
|
||||||
endpoint.String(),
|
|
||||||
rpc.WithRetryLimit(
|
|
||||||
c.Int("retry-limit"),
|
|
||||||
),
|
|
||||||
rpc.WithBackoff(
|
|
||||||
c.Duration("backoff"),
|
|
||||||
),
|
|
||||||
rpc.WithToken(
|
|
||||||
c.String("drone-secret"),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
sigterm := abool.New()
|
|
||||||
ctx := context.Background()
|
|
||||||
ctx = interrupt.WithContextFunc(ctx, func() {
|
|
||||||
println("ctrl+c received, terminating process")
|
|
||||||
sigterm.Set()
|
|
||||||
})
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
parallel := c.Int("max-procs")
|
|
||||||
wg.Add(parallel)
|
|
||||||
|
|
||||||
for i := 0; i < parallel; i++ {
|
|
||||||
go func() {
|
|
||||||
defer wg.Done()
|
|
||||||
for {
|
|
||||||
if sigterm.IsSet() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err := run(ctx, client, filter); err != nil {
|
|
||||||
log.Printf("build runner encountered error: exiting: %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxFileUpload = 5000000
|
|
||||||
maxLogsUpload = 5000000
|
|
||||||
)
|
|
||||||
|
|
||||||
func run(ctx context.Context, client rpc.Peer, filter rpc.Filter) error {
|
|
||||||
log.Println("pipeline: request next execution")
|
|
||||||
|
|
||||||
// get the next job from the queue
|
|
||||||
work, err := client.Next(ctx, filter)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if work == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
log.Printf("pipeline: received next execution: %s", work.ID)
|
|
||||||
|
|
||||||
// new docker engine
|
|
||||||
engine, err := docker.NewEnv()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := time.Hour
|
|
||||||
if minutes := work.Timeout; minutes != 0 {
|
|
||||||
timeout = time.Duration(minutes) * time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
cancelled := abool.New()
|
|
||||||
go func() {
|
|
||||||
if werr := client.Wait(ctx, work.ID); werr != nil {
|
|
||||||
cancelled.SetTo(true)
|
|
||||||
log.Printf("pipeline: cancel signal received: %s: %s", work.ID, werr)
|
|
||||||
cancel()
|
|
||||||
} else {
|
|
||||||
log.Printf("pipeline: cancel channel closed: %s", work.ID)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
log.Printf("pipeline: cancel ping loop: %s", work.ID)
|
|
||||||
return
|
|
||||||
case <-time.After(time.Minute):
|
|
||||||
log.Printf("pipeline: ping queue: %s", work.ID)
|
|
||||||
client.Extend(ctx, work.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
state := rpc.State{}
|
|
||||||
state.Started = time.Now().Unix()
|
|
||||||
err = client.Update(context.Background(), work.ID, state)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("pipeline: error updating pipeline status: %s: %s", work.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var uploads sync.WaitGroup
|
|
||||||
defaultLogger := pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
|
|
||||||
part, rerr := rc.NextPart()
|
|
||||||
if rerr != nil {
|
|
||||||
return rerr
|
|
||||||
}
|
|
||||||
uploads.Add(1)
|
|
||||||
writer := rpc.NewLineWriter(client, work.ID, proc.Alias)
|
|
||||||
rlimit := io.LimitReader(part, maxLogsUpload)
|
|
||||||
io.Copy(writer, rlimit)
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
log.Printf("pipeline: finish uploading logs: %s: step %s", work.ID, proc.Alias)
|
|
||||||
uploads.Done()
|
|
||||||
}()
|
|
||||||
|
|
||||||
part, rerr = rc.NextPart()
|
|
||||||
if rerr != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
rlimit = io.LimitReader(part, maxFileUpload)
|
|
||||||
mime := part.Header().Get("Content-Type")
|
|
||||||
if serr := client.Upload(context.Background(), work.ID, mime, rlimit); serr != nil {
|
|
||||||
log.Printf("pipeline: cannot upload artifact: %s: %s: %s", work.ID, mime, serr)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
err = pipeline.New(work.Config,
|
|
||||||
pipeline.WithContext(ctx),
|
|
||||||
pipeline.WithLogger(defaultLogger),
|
|
||||||
pipeline.WithTracer(pipeline.DefaultTracer),
|
|
||||||
pipeline.WithEngine(engine),
|
|
||||||
).Run()
|
|
||||||
|
|
||||||
state.Finished = time.Now().Unix()
|
|
||||||
state.Exited = true
|
|
||||||
if err != nil {
|
|
||||||
state.Error = err.Error()
|
|
||||||
if xerr, ok := err.(*pipeline.ExitError); ok {
|
|
||||||
state.ExitCode = xerr.Code
|
|
||||||
}
|
|
||||||
if xerr, ok := err.(*pipeline.OomError); ok {
|
|
||||||
state.ExitCode = xerr.Code
|
|
||||||
}
|
|
||||||
if cancelled.IsSet() {
|
|
||||||
state.ExitCode = 137
|
|
||||||
} else if state.ExitCode == 0 {
|
|
||||||
state.ExitCode = 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("pipeline: execution complete: %s", work.ID)
|
|
||||||
|
|
||||||
uploads.Wait()
|
|
||||||
err = client.Update(context.Background(), work.ID, state)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("Pipeine: error updating pipeline status: %s: %s", work.ID, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -1,64 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"html/template"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var agentsCmd = cli.Command{
|
|
||||||
Name: "agents",
|
|
||||||
Usage: "manage agents",
|
|
||||||
Action: func(c *cli.Context) {
|
|
||||||
if err := agentList(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "format",
|
|
||||||
Usage: "format output",
|
|
||||||
Value: tmplAgentList,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func agentList(c *cli.Context) error {
|
|
||||||
client, err := newClient(c)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
agents, err := client.AgentList()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
tmpl, err := template.New("_").Funcs(funcMap).Parse(c.String("format") + "\n")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, agent := range agents {
|
|
||||||
tmpl.Execute(os.Stdout, agent)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// template for build list information
|
|
||||||
var tmplAgentList = "\x1b[33m{{ .Address }} \x1b[0m" + `
|
|
||||||
Platform: {{ .Platform }}
|
|
||||||
Capacity: {{ .Capacity }} concurrent build(s)
|
|
||||||
Pinged: {{ since .Updated }} ago
|
|
||||||
Uptime: {{ since .Created }}
|
|
||||||
`
|
|
||||||
|
|
||||||
var funcMap = template.FuncMap{
|
|
||||||
"since": func(t int64) string {
|
|
||||||
d := time.Now().Sub(time.Unix(t, 0))
|
|
||||||
return d.String()
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/codegangsta/cli"
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
var buildCmd = cli.Command{
|
var buildCmd = cli.Command{
|
||||||
Name: "build",
|
Name: "build",
|
||||||
|
@ -1,22 +1,17 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildInfoCmd = cli.Command{
|
var buildInfoCmd = cli.Command{
|
||||||
Name: "info",
|
Name: "info",
|
||||||
Usage: "show build details",
|
Usage: "show build details",
|
||||||
Action: func(c *cli.Context) {
|
Action: buildInfo,
|
||||||
if err := buildInfo(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildLastCmd = cli.Command{
|
var buildLastCmd = cli.Command{
|
||||||
Name: "last",
|
Name: "last",
|
||||||
Usage: "show latest build details",
|
Usage: "show latest build details",
|
||||||
Action: func(c *cli.Context) {
|
Action: buildLast,
|
||||||
if err := buildLast(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildListCmd = cli.Command{
|
var buildListCmd = cli.Command{
|
||||||
Name: "list",
|
Name: "list",
|
||||||
Usage: "show build history",
|
Usage: "show build history",
|
||||||
Action: func(c *cli.Context) {
|
Action: buildList,
|
||||||
if err := buildList(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/cncd/pipeline/pipeline/rpc"
|
||||||
"github.com/drone/drone/build"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildLogsCmd = cli.Command{
|
var buildLogsCmd = cli.Command{
|
||||||
@ -61,7 +61,7 @@ func buildLogs(c *cli.Context) error {
|
|||||||
|
|
||||||
dec := json.NewDecoder(r)
|
dec := json.NewDecoder(r)
|
||||||
fmt.Printf("Logs for build %s/%s#%d.%d\n", owner, name, number, job)
|
fmt.Printf("Logs for build %s/%s#%d.%d\n", owner, name, number, job)
|
||||||
var line build.Line
|
var line rpc.Line
|
||||||
|
|
||||||
_, err = dec.Token()
|
_, err = dec.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -2,21 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildQueueCmd = cli.Command{
|
var buildQueueCmd = cli.Command{
|
||||||
Name: "queue",
|
Name: "queue",
|
||||||
Usage: "show build queue",
|
Usage: "show build queue",
|
||||||
Action: func(c *cli.Context) {
|
Action: buildQueue,
|
||||||
if err := buildQueue(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -2,21 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildStartCmd = cli.Command{
|
var buildStartCmd = cli.Command{
|
||||||
Name: "start",
|
Name: "start",
|
||||||
Usage: "start a build",
|
Usage: "start a build",
|
||||||
Action: func(c *cli.Context) {
|
Action: buildStart,
|
||||||
if err := buildStart(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "fork",
|
Name: "fork",
|
||||||
|
@ -2,20 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var buildStopCmd = cli.Command{
|
var buildStopCmd = cli.Command{
|
||||||
Name: "stop",
|
Name: "stop",
|
||||||
Usage: "stop a build",
|
Usage: "stop a build",
|
||||||
Action: func(c *cli.Context) {
|
Action: buildStop,
|
||||||
if err := buildStop(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildStop(c *cli.Context) (err error) {
|
func buildStop(c *cli.Context) (err error) {
|
||||||
|
@ -1 +0,0 @@
|
|||||||
package main
|
|
@ -3,22 +3,17 @@ package main
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var deployCmd = cli.Command{
|
var deployCmd = cli.Command{
|
||||||
Name: "deploy",
|
Name: "deploy",
|
||||||
Usage: "deploy code",
|
Usage: "deploy code",
|
||||||
Action: func(c *cli.Context) {
|
Action: deploy,
|
||||||
if err := deploy(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
724
drone/exec.go
724
drone/exec.go
@ -1,22 +1,26 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"context"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/drone/drone/agent"
|
"github.com/cncd/pipeline/pipeline"
|
||||||
"github.com/drone/drone/build/docker"
|
"github.com/cncd/pipeline/pipeline/backend"
|
||||||
"github.com/drone/drone/model"
|
"github.com/cncd/pipeline/pipeline/backend/docker"
|
||||||
"github.com/drone/drone/yaml"
|
"github.com/cncd/pipeline/pipeline/frontend"
|
||||||
|
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||||
|
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
|
||||||
|
"github.com/cncd/pipeline/pipeline/interrupt"
|
||||||
|
"github.com/cncd/pipeline/pipeline/multipart"
|
||||||
|
"github.com/drone/envsubst"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
"github.com/joho/godotenv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var execCmd = cli.Command{
|
var execCmd = cli.Command{
|
||||||
@ -33,439 +37,387 @@ var execCmd = cli.Command{
|
|||||||
Usage: "build from local directory",
|
Usage: "build from local directory",
|
||||||
EnvVar: "DRONE_LOCAL",
|
EnvVar: "DRONE_LOCAL",
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
|
||||||
Name: "secret",
|
|
||||||
Usage: "build secrets in KEY=VALUE format",
|
|
||||||
EnvVar: "DRONE_SECRET",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "secrets-file",
|
|
||||||
Usage: "build secrets file in KEY=VALUE format",
|
|
||||||
EnvVar: "DRONE_SECRETS_FILE",
|
|
||||||
},
|
|
||||||
cli.StringSliceFlag{
|
|
||||||
Name: "matrix",
|
|
||||||
Usage: "build matrix in KEY=VALUE format",
|
|
||||||
EnvVar: "DRONE_MATRIX",
|
|
||||||
},
|
|
||||||
cli.DurationFlag{
|
cli.DurationFlag{
|
||||||
Name: "timeout",
|
Name: "timeout",
|
||||||
Usage: "build timeout",
|
Usage: "build timeout",
|
||||||
Value: time.Hour,
|
Value: time.Hour,
|
||||||
EnvVar: "DRONE_TIMEOUT",
|
EnvVar: "DRONE_TIMEOUT",
|
||||||
},
|
},
|
||||||
cli.DurationFlag{
|
cli.StringSliceFlag{
|
||||||
Name: "timeout.inactivity",
|
Name: "volumes",
|
||||||
Usage: "build timeout for inactivity",
|
Usage: "build volumes",
|
||||||
Value: time.Minute * 15,
|
EnvVar: "DRONE_VOLUMES",
|
||||||
EnvVar: "DRONE_TIMEOUT_INACTIVITY",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
EnvVar: "DRONE_PLUGIN_PULL",
|
|
||||||
Name: "pull",
|
|
||||||
Usage: "always pull latest plugin images",
|
|
||||||
},
|
},
|
||||||
cli.StringSliceFlag{
|
cli.StringSliceFlag{
|
||||||
EnvVar: "DRONE_PLUGIN_PRIVILEGED",
|
Name: "privileged",
|
||||||
Name: "privileged",
|
Usage: "privileged plugins",
|
||||||
Usage: "plugins that require privileged mode",
|
|
||||||
Value: &cli.StringSlice{
|
Value: &cli.StringSlice{
|
||||||
"plugins/docker",
|
"plugins/docker",
|
||||||
"plugins/docker:*",
|
|
||||||
"plugins/gcr",
|
"plugins/gcr",
|
||||||
"plugins/gcr:*",
|
|
||||||
"plugins/ecr",
|
"plugins/ecr",
|
||||||
"plugins/ecr:*",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Docker daemon flags
|
|
||||||
|
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DOCKER_HOST",
|
|
||||||
Name: "docker-host",
|
|
||||||
Usage: "docker daemon address",
|
|
||||||
Value: "unix:///var/run/docker.sock",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
EnvVar: "DOCKER_TLS_VERIFY",
|
|
||||||
Name: "docker-tls-verify",
|
|
||||||
Usage: "docker daemon supports tlsverify",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
EnvVar: "DOCKER_CERT_PATH",
|
|
||||||
Name: "docker-cert-path",
|
|
||||||
Usage: "docker certificate directory",
|
|
||||||
Value: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Please note the below flags are mirrored in the plugin starter kit and
|
// Please note the below flags are mirrored in the pipec and
|
||||||
// should be kept synchronized.
|
// should be kept synchronized. Do not edit directly
|
||||||
// https://github.com/drone/drone-plugin-starter
|
// https://github.com/cncd/pipeline/pipec
|
||||||
//
|
//
|
||||||
|
|
||||||
|
//
|
||||||
|
// workspace default
|
||||||
|
//
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "repo.fullname",
|
Name: "workspace-base",
|
||||||
Usage: "repository full name",
|
Value: "/pipeline",
|
||||||
EnvVar: "DRONE_REPO",
|
EnvVar: "DRONE_WORKSPACE_BASE",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "repo.owner",
|
Name: "workspace-path",
|
||||||
Usage: "repository owner",
|
Value: "src",
|
||||||
EnvVar: "DRONE_REPO_OWNER",
|
EnvVar: "DRONE_WORKSPACE_PATH",
|
||||||
},
|
},
|
||||||
|
//
|
||||||
|
// netrc parameters
|
||||||
|
//
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "repo.name",
|
Name: "netrc-username",
|
||||||
Usage: "repository name",
|
|
||||||
EnvVar: "DRONE_REPO_NAME",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.type",
|
|
||||||
Value: "git",
|
|
||||||
Usage: "repository type",
|
|
||||||
EnvVar: "DRONE_REPO_SCM",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.link",
|
|
||||||
Usage: "repository link",
|
|
||||||
EnvVar: "DRONE_REPO_LINK",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.avatar",
|
|
||||||
Usage: "repository avatar",
|
|
||||||
EnvVar: "DRONE_REPO_AVATAR",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "repo.branch",
|
|
||||||
Usage: "repository default branch",
|
|
||||||
EnvVar: "DRONE_REPO_BRANCH",
|
|
||||||
},
|
|
||||||
cli.BoolFlag{
|
|
||||||
Name: "repo.private",
|
|
||||||
Usage: "repository is private",
|
|
||||||
EnvVar: "DRONE_REPO_PRIVATE",
|
|
||||||
},
|
|
||||||
cli.BoolTFlag{
|
|
||||||
Name: "repo.trusted",
|
|
||||||
Usage: "repository is trusted",
|
|
||||||
EnvVar: "DRONE_REPO_TRUSTED",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "remote.url",
|
|
||||||
Usage: "git remote url",
|
|
||||||
EnvVar: "DRONE_REMOTE_URL",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.sha",
|
|
||||||
Usage: "git commit sha",
|
|
||||||
EnvVar: "DRONE_COMMIT_SHA",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.ref",
|
|
||||||
Value: "refs/heads/master",
|
|
||||||
Usage: "git commit ref",
|
|
||||||
EnvVar: "DRONE_COMMIT_REF",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.branch",
|
|
||||||
Value: "master",
|
|
||||||
Usage: "git commit branch",
|
|
||||||
EnvVar: "DRONE_COMMIT_BRANCH",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.message",
|
|
||||||
Usage: "git commit message",
|
|
||||||
EnvVar: "DRONE_COMMIT_MESSAGE",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.link",
|
|
||||||
Usage: "git commit link",
|
|
||||||
EnvVar: "DRONE_COMMIT_LINK",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.author.name",
|
|
||||||
Usage: "git author name",
|
|
||||||
EnvVar: "DRONE_COMMIT_AUTHOR",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.author.email",
|
|
||||||
Usage: "git author email",
|
|
||||||
EnvVar: "DRONE_COMMIT_AUTHOR_EMAIL",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "commit.author.avatar",
|
|
||||||
Usage: "git author avatar",
|
|
||||||
EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "build.event",
|
|
||||||
Value: "push",
|
|
||||||
Usage: "build event",
|
|
||||||
EnvVar: "DRONE_BUILD_EVENT",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.number",
|
|
||||||
Usage: "build number",
|
|
||||||
EnvVar: "DRONE_BUILD_NUMBER",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.created",
|
|
||||||
Usage: "build created",
|
|
||||||
EnvVar: "DRONE_BUILD_CREATED",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.started",
|
|
||||||
Usage: "build started",
|
|
||||||
EnvVar: "DRONE_BUILD_STARTED",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "build.finished",
|
|
||||||
Usage: "build finished",
|
|
||||||
EnvVar: "DRONE_BUILD_FINISHED",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "build.status",
|
|
||||||
Usage: "build status",
|
|
||||||
Value: "success",
|
|
||||||
EnvVar: "DRONE_BUILD_STATUS",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "build.link",
|
|
||||||
Usage: "build link",
|
|
||||||
EnvVar: "DRONE_BUILD_LINK",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "build.deploy",
|
|
||||||
Usage: "build deployment target",
|
|
||||||
EnvVar: "DRONE_DEPLOY_TO",
|
|
||||||
},
|
|
||||||
cli.BoolTFlag{
|
|
||||||
Name: "yaml.verified",
|
|
||||||
Usage: "build yaml is verified",
|
|
||||||
EnvVar: "DRONE_YAML_VERIFIED",
|
|
||||||
},
|
|
||||||
cli.BoolTFlag{
|
|
||||||
Name: "yaml.signed",
|
|
||||||
Usage: "build yaml is signed",
|
|
||||||
EnvVar: "DRONE_YAML_SIGNED",
|
|
||||||
},
|
|
||||||
cli.IntFlag{
|
|
||||||
Name: "prev.build.number",
|
|
||||||
Usage: "previous build number",
|
|
||||||
EnvVar: "DRONE_PREV_BUILD_NUMBER",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "prev.build.status",
|
|
||||||
Usage: "previous build status",
|
|
||||||
EnvVar: "DRONE_PREV_BUILD_STATUS",
|
|
||||||
},
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "prev.commit.sha",
|
|
||||||
Usage: "previous build sha",
|
|
||||||
EnvVar: "DRONE_PREV_COMMIT_SHA",
|
|
||||||
},
|
|
||||||
|
|
||||||
cli.StringFlag{
|
|
||||||
Name: "netrc.username",
|
|
||||||
Usage: "previous build sha",
|
|
||||||
EnvVar: "DRONE_NETRC_USERNAME",
|
EnvVar: "DRONE_NETRC_USERNAME",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "netrc.password",
|
Name: "netrc-password",
|
||||||
Usage: "previous build sha",
|
|
||||||
EnvVar: "DRONE_NETRC_PASSWORD",
|
EnvVar: "DRONE_NETRC_PASSWORD",
|
||||||
},
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "netrc.machine",
|
Name: "netrc-machine",
|
||||||
Usage: "previous build sha",
|
|
||||||
EnvVar: "DRONE_NETRC_MACHINE",
|
EnvVar: "DRONE_NETRC_MACHINE",
|
||||||
},
|
},
|
||||||
|
//
|
||||||
|
// metadata parameters
|
||||||
|
//
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "system-arch",
|
||||||
|
Value: "linux/amd64",
|
||||||
|
EnvVar: "DRONE_SYSTEM_ARCH",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "system-name",
|
||||||
|
Value: "pipec",
|
||||||
|
EnvVar: "DRONE_SYSTEM_NAME",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "system-link",
|
||||||
|
Value: "https://github.com/cncd/pipec",
|
||||||
|
EnvVar: "DRONE_SYSTEM_LINK",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "repo-name",
|
||||||
|
EnvVar: "DRONE_REPO_NAME",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "repo-link",
|
||||||
|
EnvVar: "DRONE_REPO_LINK",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "repo-remote-url",
|
||||||
|
EnvVar: "DRONE_REPO_REMOTE",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "repo-private",
|
||||||
|
EnvVar: "DRONE_REPO_PRIVATE",
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "build-number",
|
||||||
|
EnvVar: "DRONE_BUILD_NUMBER",
|
||||||
|
},
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "build-created",
|
||||||
|
EnvVar: "DRONE_BUILD_CREATED",
|
||||||
|
},
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "build-started",
|
||||||
|
EnvVar: "DRONE_BUILD_STARTED",
|
||||||
|
},
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "build-finished",
|
||||||
|
EnvVar: "DRONE_BUILD_FINISHED",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "build-status",
|
||||||
|
EnvVar: "DRONE_BUILD_STATUS",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "build-event",
|
||||||
|
EnvVar: "DRONE_BUILD_EVENT",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "build-link",
|
||||||
|
EnvVar: "DRONE_BUILD_LINK",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "build-target",
|
||||||
|
EnvVar: "DRONE_BUILD_TARGET",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-sha",
|
||||||
|
EnvVar: "DRONE_COMMIT_SHA",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-ref",
|
||||||
|
EnvVar: "DRONE_COMMIT_REF",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-refspec",
|
||||||
|
EnvVar: "DRONE_COMMIT_REFSPEC",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-branch",
|
||||||
|
EnvVar: "DRONE_COMMIT_BRANCH",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-message",
|
||||||
|
EnvVar: "DRONE_COMMIT_MESSAGE",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-author-name",
|
||||||
|
EnvVar: "DRONE_COMMIT_AUTHOR_NAME",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-author-avatar",
|
||||||
|
EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "commit-author-email",
|
||||||
|
EnvVar: "DRONE_COMMIT_AUTHOR_EMAIL",
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "prev-build-number",
|
||||||
|
EnvVar: "DRONE_PREV_BUILD_NUMBER",
|
||||||
|
},
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "prev-build-created",
|
||||||
|
EnvVar: "DRONE_PREV_BUILD_CREATED",
|
||||||
|
},
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "prev-build-started",
|
||||||
|
EnvVar: "DRONE_PREV_BUILD_STARTED",
|
||||||
|
},
|
||||||
|
cli.Int64Flag{
|
||||||
|
Name: "prev-build-finished",
|
||||||
|
EnvVar: "DRONE_PREV_BUILD_FINISHED",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-build-status",
|
||||||
|
EnvVar: "DRONE_PREV_BUILD_STATUS",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-build-event",
|
||||||
|
EnvVar: "DRONE_PREV_BUILD_EVENT",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-build-link",
|
||||||
|
EnvVar: "DRONE_PREV_BUILD_LINK",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-sha",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_SHA",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-ref",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_REF",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-refspec",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_REFSPEC",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-branch",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_BRANCH",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-message",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_MESSAGE",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-author-name",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_AUTHOR_NAME",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-author-avatar",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_AUTHOR_AVATAR",
|
||||||
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "prev-commit-author-email",
|
||||||
|
EnvVar: "DRONE_PREV_COMMIT_AUTHOR_EMAIL",
|
||||||
|
},
|
||||||
|
cli.IntFlag{
|
||||||
|
Name: "job-number",
|
||||||
|
EnvVar: "DRONE_JOB_NUMBER",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func exec(c *cli.Context) error {
|
func exec(c *cli.Context) error {
|
||||||
sigterm := make(chan os.Signal, 1)
|
file := c.Args().First()
|
||||||
cancelc := make(chan bool, 1)
|
if file == "" {
|
||||||
signal.Notify(sigterm, os.Interrupt)
|
file = ".drone.yml"
|
||||||
go func() {
|
|
||||||
<-sigterm
|
|
||||||
cancelc <- true
|
|
||||||
}()
|
|
||||||
|
|
||||||
path := c.Args().First()
|
|
||||||
if path == "" {
|
|
||||||
path = ".drone.yml"
|
|
||||||
}
|
}
|
||||||
path, _ = filepath.Abs(path)
|
|
||||||
dir := filepath.Dir(path)
|
|
||||||
|
|
||||||
file, err := ioutil.ReadFile(path)
|
metadata := metadataFromContext(c)
|
||||||
|
environ := metadata.Environ()
|
||||||
|
for k, v := range metadata.EnvironDrone() {
|
||||||
|
environ[k] = v
|
||||||
|
}
|
||||||
|
for _, env := range os.Environ() {
|
||||||
|
k := strings.Split(env, "=")[0]
|
||||||
|
v := strings.Split(env, "=")[1]
|
||||||
|
environ[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpl, err := envsubst.ParseFile(file)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
confstr, err := tmpl.Execute(func(name string) string {
|
||||||
|
return environ[name]
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
engine, err := docker.New(
|
conf, err := yaml.ParseString(confstr)
|
||||||
c.String("docker-host"),
|
|
||||||
c.String("docker-cert-path"),
|
|
||||||
c.Bool("docker-tls-verify"),
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
a := agent.Agent{
|
// configure volumes for local execution
|
||||||
Update: agent.NoopUpdateFunc,
|
volumes := c.StringSlice("volumes")
|
||||||
Logger: agent.TermLoggerFunc,
|
if c.Bool("local") {
|
||||||
Engine: engine,
|
var (
|
||||||
Timeout: c.Duration("timeout.inactivity"),
|
workspaceBase = conf.Workspace.Base
|
||||||
Platform: "linux/amd64",
|
workspacePath = conf.Workspace.Path
|
||||||
Escalate: c.StringSlice("privileged"),
|
)
|
||||||
Netrc: []string{},
|
if workspaceBase == "" {
|
||||||
Local: dir,
|
workspaceBase = c.String("workspace-base")
|
||||||
Pull: c.Bool("pull"),
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := &model.Work{
|
|
||||||
Yaml: string(file),
|
|
||||||
Verified: c.BoolT("yaml.verified"),
|
|
||||||
Signed: c.BoolT("yaml.signed"),
|
|
||||||
Repo: &model.Repo{
|
|
||||||
FullName: c.String("repo.fullname"),
|
|
||||||
Owner: c.String("repo.owner"),
|
|
||||||
Name: c.String("repo.name"),
|
|
||||||
Kind: c.String("repo.type"),
|
|
||||||
Link: c.String("repo.link"),
|
|
||||||
Branch: c.String("repo.branch"),
|
|
||||||
Avatar: c.String("repo.avatar"),
|
|
||||||
Timeout: int64(c.Duration("timeout").Minutes()),
|
|
||||||
IsPrivate: c.Bool("repo.private"),
|
|
||||||
IsTrusted: c.BoolT("repo.trusted"),
|
|
||||||
Clone: c.String("remote.url"),
|
|
||||||
},
|
|
||||||
System: &model.System{
|
|
||||||
Link: c.GlobalString("server"),
|
|
||||||
},
|
|
||||||
Secrets: getSecrets(c),
|
|
||||||
Netrc: &model.Netrc{
|
|
||||||
Login: c.String("netrc.username"),
|
|
||||||
Password: c.String("netrc.password"),
|
|
||||||
Machine: c.String("netrc.machine"),
|
|
||||||
},
|
|
||||||
Build: &model.Build{
|
|
||||||
Commit: c.String("commit.sha"),
|
|
||||||
Branch: c.String("commit.branch"),
|
|
||||||
Ref: c.String("commit.ref"),
|
|
||||||
Link: c.String("commit.link"),
|
|
||||||
Message: c.String("commit.message"),
|
|
||||||
Author: c.String("commit.author.name"),
|
|
||||||
Email: c.String("commit.author.email"),
|
|
||||||
Avatar: c.String("commit.author.avatar"),
|
|
||||||
Number: c.Int("build.number"),
|
|
||||||
Event: c.String("build.event"),
|
|
||||||
Deploy: c.String("build.deploy"),
|
|
||||||
},
|
|
||||||
BuildLast: &model.Build{
|
|
||||||
Number: c.Int("prev.build.number"),
|
|
||||||
Status: c.String("prev.build.status"),
|
|
||||||
Commit: c.String("prev.commit.sha"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.StringSlice("matrix")) > 0 {
|
|
||||||
p := *payload
|
|
||||||
p.Job = &model.Job{
|
|
||||||
Environment: getMatrix(c),
|
|
||||||
}
|
}
|
||||||
return a.Run(&p, cancelc)
|
if workspacePath == "" {
|
||||||
|
workspacePath = c.String("workspace-path")
|
||||||
|
}
|
||||||
|
dir, _ := filepath.Abs(filepath.Dir(file))
|
||||||
|
volumes = append(volumes, dir+":"+path.Join(workspaceBase, workspacePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
axes, err := yaml.ParseMatrix(file)
|
// compiles the yaml file
|
||||||
|
compiled := compiler.New(
|
||||||
|
compiler.WithEscalated(
|
||||||
|
c.StringSlice("privileged")...,
|
||||||
|
),
|
||||||
|
compiler.WithVolumes(volumes...),
|
||||||
|
compiler.WithWorkspace(
|
||||||
|
c.String("workspace-base"),
|
||||||
|
c.String("workspace-path"),
|
||||||
|
),
|
||||||
|
compiler.WithPrefix(
|
||||||
|
c.String("prefix"),
|
||||||
|
),
|
||||||
|
compiler.WithProxy(),
|
||||||
|
compiler.WithLocal(
|
||||||
|
c.Bool("local"),
|
||||||
|
),
|
||||||
|
compiler.WithNetrc(
|
||||||
|
c.String("netrc-username"),
|
||||||
|
c.String("netrc-password"),
|
||||||
|
c.String("netrc-machine"),
|
||||||
|
),
|
||||||
|
compiler.WithMetadata(metadata),
|
||||||
|
).Compile(conf)
|
||||||
|
|
||||||
|
engine, err := docker.NewEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(axes) == 0 {
|
ctx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout"))
|
||||||
axes = append(axes, yaml.Axis{})
|
defer cancel()
|
||||||
}
|
ctx = interrupt.WithContext(ctx)
|
||||||
|
|
||||||
var jobs []*model.Job
|
return pipeline.New(compiled,
|
||||||
count := 0
|
pipeline.WithContext(ctx),
|
||||||
for _, axis := range axes {
|
pipeline.WithLogger(defaultLogger),
|
||||||
jobs = append(jobs, &model.Job{
|
pipeline.WithTracer(pipeline.DefaultTracer),
|
||||||
Number: count,
|
pipeline.WithLogger(defaultLogger),
|
||||||
Environment: axis,
|
pipeline.WithEngine(engine),
|
||||||
})
|
).Run()
|
||||||
count++
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, job := range jobs {
|
|
||||||
fmt.Printf("Running Matrix job #%d\n", job.Number)
|
|
||||||
p := *payload
|
|
||||||
p.Job = job
|
|
||||||
if err := a.Run(&p, cancelc); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to retrieve matrix variables.
|
// return the metadata from the cli context.
|
||||||
func getMatrix(c *cli.Context) map[string]string {
|
func metadataFromContext(c *cli.Context) frontend.Metadata {
|
||||||
envs := map[string]string{}
|
return frontend.Metadata{
|
||||||
for _, s := range c.StringSlice("matrix") {
|
Repo: frontend.Repo{
|
||||||
parts := strings.SplitN(s, "=", 2)
|
Name: c.String("repo-name"),
|
||||||
if len(parts) != 2 {
|
Link: c.String("repo-link"),
|
||||||
continue
|
Remote: c.String("repo-remote-url"),
|
||||||
}
|
Private: c.Bool("repo-private"),
|
||||||
k := parts[0]
|
},
|
||||||
v := parts[1]
|
Curr: frontend.Build{
|
||||||
envs[k] = v
|
Number: c.Int("build-number"),
|
||||||
}
|
Created: c.Int64("build-created"),
|
||||||
return envs
|
Started: c.Int64("build-started"),
|
||||||
}
|
Finished: c.Int64("build-finished"),
|
||||||
|
Status: c.String("build-status"),
|
||||||
// helper function to retrieve secret variables.
|
Event: c.String("build-event"),
|
||||||
func getSecrets(c *cli.Context) []*model.Secret {
|
Link: c.String("build-link"),
|
||||||
|
Target: c.String("build-target"),
|
||||||
var secrets []*model.Secret
|
Commit: frontend.Commit{
|
||||||
|
Sha: c.String("commit-sha"),
|
||||||
if c.String("secrets-file") != "" {
|
Ref: c.String("commit-ref"),
|
||||||
envs, _ := godotenv.Read(c.String("secrets-file"))
|
Refspec: c.String("commit-refspec"),
|
||||||
for k, v := range envs {
|
Branch: c.String("commit-branch"),
|
||||||
secret := &model.Secret{
|
Message: c.String("commit-message"),
|
||||||
Name: k,
|
Author: frontend.Author{
|
||||||
Value: v,
|
Name: c.String("commit-author-name"),
|
||||||
Events: []string{
|
Email: c.String("commit-author-email"),
|
||||||
model.EventPull,
|
Avatar: c.String("commit-author-avatar"),
|
||||||
model.EventPush,
|
|
||||||
model.EventTag,
|
|
||||||
model.EventDeploy,
|
|
||||||
},
|
},
|
||||||
Images: []string{"*"},
|
|
||||||
}
|
|
||||||
secrets = append(secrets, secret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range c.StringSlice("secret") {
|
|
||||||
parts := strings.SplitN(s, "=", 2)
|
|
||||||
if len(parts) != 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
secret := &model.Secret{
|
|
||||||
Name: parts[0],
|
|
||||||
Value: parts[1],
|
|
||||||
Events: []string{
|
|
||||||
model.EventPull,
|
|
||||||
model.EventPush,
|
|
||||||
model.EventTag,
|
|
||||||
model.EventDeploy,
|
|
||||||
},
|
},
|
||||||
Images: []string{"*"},
|
},
|
||||||
}
|
Prev: frontend.Build{
|
||||||
secrets = append(secrets, secret)
|
Number: c.Int("prev-build-number"),
|
||||||
|
Created: c.Int64("prev-build-created"),
|
||||||
|
Started: c.Int64("prev-build-started"),
|
||||||
|
Finished: c.Int64("prev-build-finished"),
|
||||||
|
Status: c.String("prev-build-status"),
|
||||||
|
Event: c.String("prev-build-event"),
|
||||||
|
Link: c.String("prev-build-link"),
|
||||||
|
Commit: frontend.Commit{
|
||||||
|
Sha: c.String("prev-commit-sha"),
|
||||||
|
Ref: c.String("prev-commit-ref"),
|
||||||
|
Refspec: c.String("prev-commit-refspec"),
|
||||||
|
Branch: c.String("prev-commit-branch"),
|
||||||
|
Message: c.String("prev-commit-message"),
|
||||||
|
Author: frontend.Author{
|
||||||
|
Name: c.String("prev-commit-author-name"),
|
||||||
|
Email: c.String("prev-commit-author-email"),
|
||||||
|
Avatar: c.String("prev-commit-author-avatar"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Job: frontend.Job{
|
||||||
|
Number: c.Int("job-number"),
|
||||||
|
},
|
||||||
|
Sys: frontend.System{
|
||||||
|
Name: c.String("system-name"),
|
||||||
|
Link: c.String("system-link"),
|
||||||
|
Arch: c.String("system-arch"),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
return secrets
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var defaultLogger = pipeline.LogFunc(func(proc *backend.Step, rc multipart.Reader) error {
|
||||||
|
part, err := rc.NextPart()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
io.Copy(os.Stderr, part)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/codegangsta/cli"
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
var globalCmd = cli.Command{
|
var globalCmd = cli.Command{
|
||||||
Name: "global",
|
Name: "global",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/codegangsta/cli"
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
var globalSecretCmd = cli.Command{
|
var globalSecretCmd = cli.Command{
|
||||||
Name: "secret",
|
Name: "secret",
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalSecretAddCmd = cli.Command{
|
var globalSecretAddCmd = cli.Command{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Usage: "adds a secret",
|
Usage: "adds a secret",
|
||||||
ArgsUsage: "[key] [value]",
|
ArgsUsage: "[key] [value]",
|
||||||
Action: func(c *cli.Context) {
|
Action: globalSecretAdd,
|
||||||
if err := globalSecretAdd(c); err != nil {
|
Flags: secretAddFlags(),
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: secretAddFlags(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func globalSecretAdd(c *cli.Context) error {
|
func globalSecretAdd(c *cli.Context) error {
|
||||||
|
@ -1,20 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalSecretListCmd = cli.Command{
|
var globalSecretListCmd = cli.Command{
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "list all secrets",
|
Usage: "list all secrets",
|
||||||
Action: func(c *cli.Context) {
|
Action: globalSecretList,
|
||||||
if err := globalSecretList(c); err != nil {
|
Flags: secretListFlags(),
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: secretListFlags(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func globalSecretList(c *cli.Context) error {
|
func globalSecretList(c *cli.Context) error {
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var globalSecretRemoveCmd = cli.Command{
|
var globalSecretRemoveCmd = cli.Command{
|
||||||
Name: "rm",
|
Name: "rm",
|
||||||
Usage: "remove a secret",
|
Usage: "remove a secret",
|
||||||
Action: func(c *cli.Context) {
|
Action: globalSecretRemove,
|
||||||
if err := globalSecretRemove(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func globalSecretRemove(c *cli.Context) error {
|
func globalSecretRemove(c *cli.Context) error {
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var infoCmd = cli.Command{
|
var infoCmd = cli.Command{
|
||||||
Name: "info",
|
Name: "info",
|
||||||
Usage: "show information about the current user",
|
Usage: "show information about the current user",
|
||||||
Action: func(c *cli.Context) {
|
Action: info,
|
||||||
if err := info(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/drone/drone/drone/agent"
|
"github.com/drone/drone/drone/agent"
|
||||||
"github.com/drone/drone/version"
|
"github.com/drone/drone/version"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/ianschenck/envflag"
|
"github.com/ianschenck/envflag"
|
||||||
_ "github.com/joho/godotenv/autoload"
|
_ "github.com/joho/godotenv/autoload"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -32,7 +33,6 @@ func main() {
|
|||||||
}
|
}
|
||||||
app.Commands = []cli.Command{
|
app.Commands = []cli.Command{
|
||||||
agent.AgentCmd,
|
agent.AgentCmd,
|
||||||
agentsCmd,
|
|
||||||
buildCmd,
|
buildCmd,
|
||||||
deployCmd,
|
deployCmd,
|
||||||
execCmd,
|
execCmd,
|
||||||
@ -46,5 +46,8 @@ func main() {
|
|||||||
globalCmd,
|
globalCmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Run(os.Args)
|
if err := app.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(os.Stderr, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/codegangsta/cli"
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
var orgCmd = cli.Command{
|
var orgCmd = cli.Command{
|
||||||
Name: "org",
|
Name: "org",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/codegangsta/cli"
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
var orgSecretCmd = cli.Command{
|
var orgSecretCmd = cli.Command{
|
||||||
Name: "secret",
|
Name: "secret",
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var orgSecretAddCmd = cli.Command{
|
var orgSecretAddCmd = cli.Command{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Usage: "adds a secret",
|
Usage: "adds a secret",
|
||||||
ArgsUsage: "[org] [key] [value]",
|
ArgsUsage: "[org] [key] [value]",
|
||||||
Action: func(c *cli.Context) {
|
Action: orgSecretAdd,
|
||||||
if err := orgSecretAdd(c); err != nil {
|
Flags: secretAddFlags(),
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: secretAddFlags(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func orgSecretAdd(c *cli.Context) error {
|
func orgSecretAdd(c *cli.Context) error {
|
||||||
|
@ -1,20 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var orgSecretListCmd = cli.Command{
|
var orgSecretListCmd = cli.Command{
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "list all secrets",
|
Usage: "list all secrets",
|
||||||
Action: func(c *cli.Context) {
|
Action: orgSecretList,
|
||||||
if err := orgSecretList(c); err != nil {
|
Flags: secretListFlags(),
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: secretListFlags(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func orgSecretList(c *cli.Context) error {
|
func orgSecretList(c *cli.Context) error {
|
||||||
|
@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var orgSecretRemoveCmd = cli.Command{
|
var orgSecretRemoveCmd = cli.Command{
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/codegangsta/cli"
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
var repoCmd = cli.Command{
|
var repoCmd = cli.Command{
|
||||||
Name: "repo",
|
Name: "repo",
|
||||||
|
@ -2,19 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var repoAddCmd = cli.Command{
|
var repoAddCmd = cli.Command{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Usage: "add a repository",
|
Usage: "add a repository",
|
||||||
Action: func(c *cli.Context) {
|
Action: repoAdd,
|
||||||
if err := repoAdd(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoAdd(c *cli.Context) error {
|
func repoAdd(c *cli.Context) error {
|
||||||
|
@ -2,19 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var repoChownCmd = cli.Command{
|
var repoChownCmd = cli.Command{
|
||||||
Name: "chown",
|
Name: "chown",
|
||||||
Usage: "assume ownership of a repository",
|
Usage: "assume ownership of a repository",
|
||||||
Action: func(c *cli.Context) {
|
Action: repoChown,
|
||||||
if err := repoChown(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoChown(c *cli.Context) error {
|
func repoChown(c *cli.Context) error {
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var repoInfoCmd = cli.Command{
|
var repoInfoCmd = cli.Command{
|
||||||
Name: "info",
|
Name: "info",
|
||||||
Usage: "show repository details",
|
Usage: "show repository details",
|
||||||
Action: func(c *cli.Context) {
|
Action: repoInfo,
|
||||||
if err := repoInfo(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var repoListCmd = cli.Command{
|
var repoListCmd = cli.Command{
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "list all repos",
|
Usage: "list all repos",
|
||||||
Action: func(c *cli.Context) {
|
Action: repoList,
|
||||||
if err := repoList(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -2,19 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var repoRemoveCmd = cli.Command{
|
var repoRemoveCmd = cli.Command{
|
||||||
Name: "rm",
|
Name: "rm",
|
||||||
Usage: "remove a repository",
|
Usage: "remove a repository",
|
||||||
Action: func(c *cli.Context) {
|
Action: repoRemove,
|
||||||
if err := repoRemove(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func repoRemove(c *cli.Context) error {
|
func repoRemove(c *cli.Context) error {
|
||||||
|
@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var secretCmd = cli.Command{
|
var secretCmd = cli.Command{
|
||||||
|
@ -1,21 +1,13 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var secretAddCmd = cli.Command{
|
var secretAddCmd = cli.Command{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Usage: "adds a secret",
|
Usage: "adds a secret",
|
||||||
ArgsUsage: "[repo] [key] [value]",
|
ArgsUsage: "[repo] [key] [value]",
|
||||||
Action: func(c *cli.Context) {
|
Action: secretAdd,
|
||||||
if err := secretAdd(c); err != nil {
|
Flags: secretAddFlags(),
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: secretAddFlags(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func secretAdd(c *cli.Context) error {
|
func secretAdd(c *cli.Context) error {
|
||||||
|
@ -1,20 +1,12 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var secretListCmd = cli.Command{
|
var secretListCmd = cli.Command{
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "list all secrets",
|
Usage: "list all secrets",
|
||||||
Action: func(c *cli.Context) {
|
Action: secretList,
|
||||||
if err := secretList(c); err != nil {
|
Flags: secretListFlags(),
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: secretListFlags(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func secretList(c *cli.Context) error {
|
func secretList(c *cli.Context) error {
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import "github.com/urfave/cli"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
)
|
|
||||||
|
|
||||||
var secretRemoveCmd = cli.Command{
|
var secretRemoveCmd = cli.Command{
|
||||||
Name: "rm",
|
Name: "rm",
|
||||||
Usage: "remove a secret",
|
Usage: "remove a secret",
|
||||||
Action: func(c *cli.Context) {
|
Action: secretRemove,
|
||||||
if err := secretRemove(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func secretRemove(c *cli.Context) error {
|
func secretRemove(c *cli.Context) error {
|
||||||
|
@ -8,18 +8,14 @@ import (
|
|||||||
"github.com/drone/drone/router/middleware"
|
"github.com/drone/drone/router/middleware"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/gin-gonic/contrib/ginrus"
|
"github.com/gin-gonic/contrib/ginrus"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var serverCmd = cli.Command{
|
var serverCmd = cli.Command{
|
||||||
Name: "server",
|
Name: "server",
|
||||||
Usage: "starts the drone server daemon",
|
Usage: "starts the drone server daemon",
|
||||||
Action: func(c *cli.Context) {
|
Action: server,
|
||||||
if err := server(c); err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
EnvVar: "DRONE_DEBUG",
|
EnvVar: "DRONE_DEBUG",
|
||||||
@ -301,8 +297,6 @@ func server(c *cli.Context) error {
|
|||||||
middleware.Cache(c),
|
middleware.Cache(c),
|
||||||
middleware.Store(c),
|
middleware.Store(c),
|
||||||
middleware.Remote(c),
|
middleware.Remote(c),
|
||||||
middleware.Agents(c),
|
|
||||||
middleware.Broker(c),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// start the server with tls enabled
|
// start the server with tls enabled
|
||||||
|
@ -2,19 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var signCmd = cli.Command{
|
var signCmd = cli.Command{
|
||||||
Name: "sign",
|
Name: "sign",
|
||||||
Usage: "creates a secure yaml file",
|
Usage: "creates a secure yaml file",
|
||||||
Action: func(c *cli.Context) {
|
Action: sign,
|
||||||
if err := sign(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "in",
|
Name: "in",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/codegangsta/cli"
|
import "github.com/urfave/cli"
|
||||||
|
|
||||||
var userCmd = cli.Command{
|
var userCmd = cli.Command{
|
||||||
Name: "user",
|
Name: "user",
|
||||||
|
@ -2,20 +2,15 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var userAddCmd = cli.Command{
|
var userAddCmd = cli.Command{
|
||||||
Name: "add",
|
Name: "add",
|
||||||
Usage: "adds a user",
|
Usage: "adds a user",
|
||||||
Action: func(c *cli.Context) {
|
Action: userAdd,
|
||||||
if err := userAdd(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func userAdd(c *cli.Context) error {
|
func userAdd(c *cli.Context) error {
|
||||||
|
@ -2,21 +2,16 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var userInfoCmd = cli.Command{
|
var userInfoCmd = cli.Command{
|
||||||
Name: "info",
|
Name: "info",
|
||||||
Usage: "show user details",
|
Usage: "show user details",
|
||||||
Action: func(c *cli.Context) {
|
Action: userInfo,
|
||||||
if err := userInfo(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var userListCmd = cli.Command{
|
var userListCmd = cli.Command{
|
||||||
Name: "ls",
|
Name: "ls",
|
||||||
Usage: "list all users",
|
Usage: "list all users",
|
||||||
Action: func(c *cli.Context) {
|
Action: userList,
|
||||||
if err := userList(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "format",
|
Name: "format",
|
||||||
|
@ -2,19 +2,14 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
var userRemoveCmd = cli.Command{
|
var userRemoveCmd = cli.Command{
|
||||||
Name: "rm",
|
Name: "rm",
|
||||||
Usage: "remove a user",
|
Usage: "remove a user",
|
||||||
Action: func(c *cli.Context) {
|
Action: userRemove,
|
||||||
if err := userRemove(c); err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func userRemove(c *cli.Context) error {
|
func userRemove(c *cli.Context) error {
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
|
|
||||||
"github.com/drone/drone/client"
|
"github.com/drone/drone/client"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/jackspirou/syscerts"
|
"github.com/jackspirou/syscerts"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newClient(c *cli.Context) (client.Client, error) {
|
func newClient(c *cli.Context) (client.Client, error) {
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/drone/shared/token"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
)
|
|
||||||
|
|
||||||
const agentKey = "agent"
|
|
||||||
|
|
||||||
// Agents is a middleware function that initializes the authorization middleware
|
|
||||||
// for agents to connect to the queue.
|
|
||||||
func Agents(cli *cli.Context) gin.HandlerFunc {
|
|
||||||
secret := cli.String("agent-secret")
|
|
||||||
if secret == "" {
|
|
||||||
logrus.Fatalf("failed to generate token from DRONE_AGENT_SECRET")
|
|
||||||
}
|
|
||||||
|
|
||||||
t := token.New(token.AgentToken, secret)
|
|
||||||
s, err := t.Sign(secret)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatalf("failed to generate token from DRONE_AGENT_SECRET. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
logrus.Infof("using agent secret %s", secret)
|
|
||||||
logrus.Warnf("agents can connect with token %s", s)
|
|
||||||
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.Set(agentKey, secret)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
package middleware
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
handlers "github.com/drone/drone/server"
|
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/mq/logger"
|
|
||||||
"github.com/drone/mq/server"
|
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/tidwall/redlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
serverKey = "broker"
|
|
||||||
clientKey = "stomp.client" // mirrored from stomp/context
|
|
||||||
)
|
|
||||||
|
|
||||||
// Broker is a middleware function that initializes the broker
|
|
||||||
// and adds the broker client to the request context.
|
|
||||||
func Broker(cli *cli.Context) gin.HandlerFunc {
|
|
||||||
secret := cli.String("agent-secret")
|
|
||||||
if secret == "" {
|
|
||||||
logrus.Fatalf("fatal error. please provide the DRONE_SECRET")
|
|
||||||
}
|
|
||||||
|
|
||||||
// setup broker logging.
|
|
||||||
log := redlog.New(os.Stderr)
|
|
||||||
log.SetLevel(2)
|
|
||||||
logger.SetLogger(log)
|
|
||||||
if cli.Bool("broker-debug") {
|
|
||||||
log.SetLevel(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
broker := server.NewServer(
|
|
||||||
server.WithCredentials("x-token", secret),
|
|
||||||
)
|
|
||||||
client := broker.Client()
|
|
||||||
|
|
||||||
var once sync.Once
|
|
||||||
return func(c *gin.Context) {
|
|
||||||
c.Set(serverKey, broker)
|
|
||||||
c.Set(clientKey, client)
|
|
||||||
once.Do(func() {
|
|
||||||
// this is some really hacky stuff
|
|
||||||
// turns out I need to do some refactoring
|
|
||||||
// don't judge!
|
|
||||||
// will fix in 0.6 release
|
|
||||||
ctx := c.Copy()
|
|
||||||
client.Connect(
|
|
||||||
stomp.WithCredentials("x-token", secret),
|
|
||||||
)
|
|
||||||
client.Subscribe("/queue/updates", stomp.HandlerFunc(func(m *stomp.Message) {
|
|
||||||
go handlers.HandleUpdate(ctx, m.Copy())
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,8 +3,8 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"github.com/drone/drone/cache"
|
"github.com/drone/drone/cache"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Cache is a middleware function that initializes the Cache and attaches to
|
// Cache is a middleware function that initializes the Cache and attaches to
|
||||||
|
@ -3,8 +3,8 @@ package middleware
|
|||||||
import (
|
import (
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
const configKey = "config"
|
const configKey = "config"
|
||||||
|
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/remote/bitbucket"
|
"github.com/drone/drone/remote/bitbucket"
|
||||||
"github.com/drone/drone/remote/bitbucketserver"
|
"github.com/drone/drone/remote/bitbucketserver"
|
||||||
@ -12,6 +11,7 @@ import (
|
|||||||
"github.com/drone/drone/remote/gitlab"
|
"github.com/drone/drone/remote/gitlab"
|
||||||
"github.com/drone/drone/remote/gogs"
|
"github.com/drone/drone/remote/gogs"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/urfave/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Remote is a middleware function that initializes the Remote and attaches to
|
// Remote is a middleware function that initializes the Remote and attaches to
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/codegangsta/cli"
|
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
"github.com/drone/drone/store/datastore"
|
"github.com/drone/drone/store/datastore"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
@ -2,7 +2,6 @@ package router
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
@ -41,8 +40,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||||||
e.GET("/logout", server.GetLogout)
|
e.GET("/logout", server.GetLogout)
|
||||||
e.NoRoute(server.ShowIndex)
|
e.NoRoute(server.ShowIndex)
|
||||||
|
|
||||||
// TODO above will Go away with React UI
|
|
||||||
|
|
||||||
user := e.Group("/api/user")
|
user := e.Group("/api/user")
|
||||||
{
|
{
|
||||||
user.Use(session.MustUser())
|
user.Use(session.MustUser())
|
||||||
@ -121,46 +118,28 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||||||
badges.GET("/cc.xml", server.GetCC)
|
badges.GET("/cc.xml", server.GetCC)
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("DRONE_CANARY") == "" {
|
e.POST("/hook", server.PostHook)
|
||||||
e.POST("/hook", server.PostHook)
|
e.POST("/api/hook", server.PostHook)
|
||||||
e.POST("/api/hook", server.PostHook)
|
|
||||||
} else {
|
ws := e.Group("/ws")
|
||||||
e.POST("/hook", server.PostHook2)
|
{
|
||||||
e.POST("/api/hook", server.PostHook2)
|
ws.GET("/broker", server.RPCHandler)
|
||||||
|
ws.GET("/rpc", server.RPCHandler)
|
||||||
|
ws.GET("/feed", server.EventStream)
|
||||||
|
ws.GET("/logs/:owner/:name/:build/:number",
|
||||||
|
session.SetRepo(),
|
||||||
|
session.SetPerm(),
|
||||||
|
session.MustPull,
|
||||||
|
server.LogStream,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("DRONE_CANARY") == "" {
|
info := e.Group("/api/info")
|
||||||
ws := e.Group("/ws")
|
{
|
||||||
{
|
info.GET("/queue",
|
||||||
ws.GET("/broker", server.Broker)
|
session.MustAdmin(),
|
||||||
ws.GET("/feed", server.EventStream)
|
server.GetQueueInfo,
|
||||||
ws.GET("/logs/:owner/:name/:build/:number",
|
)
|
||||||
session.SetRepo(),
|
|
||||||
session.SetPerm(),
|
|
||||||
session.MustPull,
|
|
||||||
server.LogStream,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ws := e.Group("/ws")
|
|
||||||
{
|
|
||||||
ws.GET("/broker", server.RPCHandler)
|
|
||||||
ws.GET("/rpc", server.RPCHandler)
|
|
||||||
ws.GET("/feed", server.EventStream2)
|
|
||||||
ws.GET("/logs/:owner/:name/:build/:number",
|
|
||||||
session.SetRepo(),
|
|
||||||
session.SetPerm(),
|
|
||||||
session.MustPull,
|
|
||||||
server.LogStream2,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
info := e.Group("/api/info")
|
|
||||||
{
|
|
||||||
info.GET("/queue",
|
|
||||||
session.MustAdmin(),
|
|
||||||
server.GetQueueInfo,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
auth := e.Group("/authorize")
|
auth := e.Group("/authorize")
|
||||||
@ -191,12 +170,5 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||||||
debugger.GET("/pprof/trace", debug.TraceHandler())
|
debugger.GET("/pprof/trace", debug.TraceHandler())
|
||||||
}
|
}
|
||||||
|
|
||||||
// bots := e.Group("/bots")
|
|
||||||
// {
|
|
||||||
// bots.Use(session.MustUser())
|
|
||||||
// bots.POST("/slack", Slack)
|
|
||||||
// bots.POST("/slack/:command", Slack)
|
|
||||||
// }
|
|
||||||
|
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
227
server/build.go
227
server/build.go
@ -7,7 +7,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -18,13 +17,10 @@ import (
|
|||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
"github.com/drone/drone/yaml"
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/square/go-jose"
|
|
||||||
|
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetBuilds(c *gin.Context) {
|
func GetBuilds(c *gin.Context) {
|
||||||
@ -156,229 +152,10 @@ func DeleteBuild(c *gin.Context) {
|
|||||||
job.ExitCode = 137
|
job.ExitCode = 137
|
||||||
store.UpdateBuildJob(c, build, job)
|
store.UpdateBuildJob(c, build, job)
|
||||||
|
|
||||||
if os.Getenv("DRONE_CANARY") == "" {
|
config.queue.Error(context.Background(), fmt.Sprint(job.ID), queue.ErrCancel)
|
||||||
client := stomp.MustFromContext(c)
|
|
||||||
client.SendJSON("/topic/cancel", model.Event{
|
|
||||||
Type: model.Cancelled,
|
|
||||||
Repo: *repo,
|
|
||||||
Build: *build,
|
|
||||||
Job: *job,
|
|
||||||
}, stomp.WithHeader("job-id", strconv.FormatInt(job.ID, 10)))
|
|
||||||
} else {
|
|
||||||
config.queue.Error(context.Background(), fmt.Sprint(job.ID), queue.ErrCancel)
|
|
||||||
}
|
|
||||||
c.String(204, "")
|
c.String(204, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostBuild(c *gin.Context) {
|
|
||||||
|
|
||||||
if os.Getenv("DRONE_CANARY") == "true" {
|
|
||||||
PostBuild2(c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
remote_ := remote.FromContext(c)
|
|
||||||
repo := session.Repo(c)
|
|
||||||
fork := c.DefaultQuery("fork", "false")
|
|
||||||
|
|
||||||
num, err := strconv.Atoi(c.Param("number"))
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithError(http.StatusBadRequest, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := store.GetUser(c, repo.UserID)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
build, err := store.GetBuildNumber(c, repo, num)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to get build %d. %s", num, err)
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the remote has a refresh token, the current access token
|
|
||||||
// may be stale. Therefore, we should refresh prior to dispatching
|
|
||||||
// the job.
|
|
||||||
if refresher, ok := remote_.(remote.Refresher); ok {
|
|
||||||
ok, _ := refresher.Refresh(user)
|
|
||||||
if ok {
|
|
||||||
store.UpdateUser(c, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch the .drone.yml file from the database
|
|
||||||
cfg := ToConfig(c)
|
|
||||||
raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch secrets file but don't exit on error as it's optional
|
|
||||||
sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
netrc, err := remote_.Netrc(user, repo)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to generate netrc for %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
jobs, err := store.GetJobList(c, build)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to get build %d jobs. %s", build.Number, err)
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// must not restart a running build
|
|
||||||
if build.Status == model.StatusPending || build.Status == model.StatusRunning {
|
|
||||||
c.String(409, "Cannot re-start a started build")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// forking the build creates a duplicate of the build
|
|
||||||
// and then executes. This retains prior build history.
|
|
||||||
if forkit, _ := strconv.ParseBool(fork); forkit {
|
|
||||||
build.ID = 0
|
|
||||||
build.Number = 0
|
|
||||||
build.Parent = num
|
|
||||||
for _, job := range jobs {
|
|
||||||
job.ID = 0
|
|
||||||
job.NodeID = 0
|
|
||||||
}
|
|
||||||
err := store.CreateBuild(c, build, jobs...)
|
|
||||||
if err != nil {
|
|
||||||
c.String(500, err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
event := c.DefaultQuery("event", build.Event)
|
|
||||||
if event == model.EventPush ||
|
|
||||||
event == model.EventPull ||
|
|
||||||
event == model.EventTag ||
|
|
||||||
event == model.EventDeploy {
|
|
||||||
build.Event = event
|
|
||||||
}
|
|
||||||
build.Deploy = c.DefaultQuery("deploy_to", build.Deploy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read query string parameters into buildParams, exclude reserved params
|
|
||||||
var buildParams = map[string]string{}
|
|
||||||
for key, val := range c.Request.URL.Query() {
|
|
||||||
switch key {
|
|
||||||
case "fork", "event", "deploy_to":
|
|
||||||
default:
|
|
||||||
// We only accept string literals, because build parameters will be
|
|
||||||
// injected as environment variables
|
|
||||||
buildParams[key] = val[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// todo move this to database tier
|
|
||||||
// and wrap inside a transaction
|
|
||||||
build.Status = model.StatusPending
|
|
||||||
build.Started = 0
|
|
||||||
build.Finished = 0
|
|
||||||
build.Enqueued = time.Now().UTC().Unix()
|
|
||||||
build.Error = ""
|
|
||||||
for _, job := range jobs {
|
|
||||||
for k, v := range buildParams {
|
|
||||||
job.Environment[k] = v
|
|
||||||
}
|
|
||||||
job.Error = ""
|
|
||||||
job.Status = model.StatusPending
|
|
||||||
job.Started = 0
|
|
||||||
job.Finished = 0
|
|
||||||
job.ExitCode = 0
|
|
||||||
job.NodeID = 0
|
|
||||||
job.Enqueued = build.Enqueued
|
|
||||||
store.UpdateJob(c, job)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = store.UpdateBuild(c, build)
|
|
||||||
if err != nil {
|
|
||||||
c.AbortWithStatus(500)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(202, build)
|
|
||||||
|
|
||||||
// get the previous build so that we can send
|
|
||||||
// on status change notifications
|
|
||||||
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
|
||||||
secs, err := store.GetMergedSecretList(c, repo)
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var signed bool
|
|
||||||
var verified bool
|
|
||||||
|
|
||||||
signature, err := jose.ParseSigned(string(sec))
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
|
||||||
} else if len(sec) == 0 {
|
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
|
||||||
} else {
|
|
||||||
signed = true
|
|
||||||
output, err := signature.Verify([]byte(repo.Hash))
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("cannot verify .drone.yml.sig file. %s", err)
|
|
||||||
} else if string(output) != string(raw) {
|
|
||||||
log.Debugf("cannot verify .drone.yml.sig file. no match. %q <> %q", string(output), string(raw))
|
|
||||||
} else {
|
|
||||||
verified = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf(".drone.yml is signed=%v and verified=%v", signed, verified)
|
|
||||||
|
|
||||||
client := stomp.MustFromContext(c)
|
|
||||||
client.SendJSON("/topic/events", model.Event{
|
|
||||||
Type: model.Enqueued,
|
|
||||||
Repo: *repo,
|
|
||||||
Build: *build,
|
|
||||||
},
|
|
||||||
stomp.WithHeader("repo", repo.FullName),
|
|
||||||
stomp.WithHeader("private", strconv.FormatBool(repo.IsPrivate)),
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, job := range jobs {
|
|
||||||
broker, _ := stomp.FromContext(c)
|
|
||||||
broker.SendJSON("/queue/pending", &model.Work{
|
|
||||||
Signed: signed,
|
|
||||||
Verified: verified,
|
|
||||||
User: user,
|
|
||||||
Repo: repo,
|
|
||||||
Build: build,
|
|
||||||
BuildLast: last,
|
|
||||||
Job: job,
|
|
||||||
Netrc: netrc,
|
|
||||||
Yaml: string(raw),
|
|
||||||
Secrets: secs,
|
|
||||||
System: &model.System{Link: httputil.GetURL(c.Request)},
|
|
||||||
},
|
|
||||||
stomp.WithHeader(
|
|
||||||
"platform",
|
|
||||||
yaml.ParsePlatformDefault(raw, "linux/amd64"),
|
|
||||||
),
|
|
||||||
stomp.WithHeaders(
|
|
||||||
yaml.ParseLabel(raw),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBuildQueue(c *gin.Context) {
|
func GetBuildQueue(c *gin.Context) {
|
||||||
out, err := store.GetBuildQueue(c)
|
out, err := store.GetBuildQueue(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -412,7 +189,7 @@ func copyLogs(dest io.Writer, src io.Reader) error {
|
|||||||
//
|
//
|
||||||
//
|
//
|
||||||
|
|
||||||
func PostBuild2(c *gin.Context) {
|
func PostBuild(c *gin.Context) {
|
||||||
|
|
||||||
remote_ := remote.FromContext(c)
|
remote_ := remote.FromContext(c)
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
|
436
server/hook.go
436
server/hook.go
@ -1,28 +1,268 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/square/go-jose"
|
"github.com/square/go-jose"
|
||||||
|
|
||||||
log "github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/remote"
|
"github.com/drone/drone/remote"
|
||||||
"github.com/drone/drone/shared/httputil"
|
"github.com/drone/drone/shared/httputil"
|
||||||
"github.com/drone/drone/shared/token"
|
"github.com/drone/drone/shared/token"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
"github.com/drone/drone/yaml"
|
"github.com/drone/envsubst"
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
|
"github.com/cncd/pipeline/pipeline/backend"
|
||||||
|
"github.com/cncd/pipeline/pipeline/frontend"
|
||||||
|
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
||||||
|
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
|
||||||
|
"github.com/cncd/pipeline/pipeline/frontend/yaml/linter"
|
||||||
|
"github.com/cncd/pipeline/pipeline/frontend/yaml/matrix"
|
||||||
|
"github.com/cncd/pipeline/pipeline/rpc"
|
||||||
|
"github.com/cncd/pubsub"
|
||||||
|
"github.com/cncd/queue"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//
|
||||||
|
// CANARY IMPLEMENTATION
|
||||||
|
//
|
||||||
|
// This file is a complete disaster because I'm trying to wedge in some
|
||||||
|
// experimental code. Please pardon our appearance during renovations.
|
||||||
|
//
|
||||||
|
|
||||||
|
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
|
||||||
|
|
||||||
|
func GetQueueInfo(c *gin.Context) {
|
||||||
|
c.IndentedJSON(200,
|
||||||
|
config.queue.Info(c),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the metadata from the cli context.
|
||||||
|
func metadataFromStruct(repo *model.Repo, build, last *model.Build, job *model.Job, link string) frontend.Metadata {
|
||||||
|
return frontend.Metadata{
|
||||||
|
Repo: frontend.Repo{
|
||||||
|
Name: repo.Name,
|
||||||
|
Link: repo.Link,
|
||||||
|
Remote: repo.Clone,
|
||||||
|
Private: repo.IsPrivate,
|
||||||
|
},
|
||||||
|
Curr: frontend.Build{
|
||||||
|
Number: build.Number,
|
||||||
|
Created: build.Created,
|
||||||
|
Started: build.Started,
|
||||||
|
Finished: build.Finished,
|
||||||
|
Status: build.Status,
|
||||||
|
Event: build.Event,
|
||||||
|
Link: build.Link,
|
||||||
|
Target: build.Deploy,
|
||||||
|
Commit: frontend.Commit{
|
||||||
|
Sha: build.Commit,
|
||||||
|
Ref: build.Ref,
|
||||||
|
Refspec: build.Refspec,
|
||||||
|
Branch: build.Branch,
|
||||||
|
Message: build.Message,
|
||||||
|
Author: frontend.Author{
|
||||||
|
Name: build.Author,
|
||||||
|
Email: build.Email,
|
||||||
|
Avatar: build.Avatar,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Prev: frontend.Build{
|
||||||
|
Number: last.Number,
|
||||||
|
Created: last.Created,
|
||||||
|
Started: last.Started,
|
||||||
|
Finished: last.Finished,
|
||||||
|
Status: last.Status,
|
||||||
|
Event: last.Event,
|
||||||
|
Link: last.Link,
|
||||||
|
Target: last.Deploy,
|
||||||
|
Commit: frontend.Commit{
|
||||||
|
Sha: last.Commit,
|
||||||
|
Ref: last.Ref,
|
||||||
|
Refspec: last.Refspec,
|
||||||
|
Branch: last.Branch,
|
||||||
|
Message: last.Message,
|
||||||
|
Author: frontend.Author{
|
||||||
|
Name: last.Author,
|
||||||
|
Email: last.Email,
|
||||||
|
Avatar: last.Avatar,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Job: frontend.Job{
|
||||||
|
Number: job.Number,
|
||||||
|
Matrix: job.Environment,
|
||||||
|
},
|
||||||
|
Sys: frontend.System{
|
||||||
|
Name: "drone",
|
||||||
|
Link: link,
|
||||||
|
Arch: "linux/amd64",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type builder struct {
|
||||||
|
Repo *model.Repo
|
||||||
|
Curr *model.Build
|
||||||
|
Last *model.Build
|
||||||
|
Netrc *model.Netrc
|
||||||
|
Secs []*model.Secret
|
||||||
|
Link string
|
||||||
|
Yaml string
|
||||||
|
}
|
||||||
|
|
||||||
|
type buildItem struct {
|
||||||
|
Job *model.Job
|
||||||
|
Platform string
|
||||||
|
Labels map[string]string
|
||||||
|
Config *backend.Config
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *builder) Build() ([]*buildItem, error) {
|
||||||
|
|
||||||
|
axes, err := matrix.ParseString(b.Yaml)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(axes) == 0 {
|
||||||
|
axes = append(axes, matrix.Axis{})
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []*buildItem
|
||||||
|
for i, axis := range axes {
|
||||||
|
job := &model.Job{
|
||||||
|
BuildID: b.Curr.ID,
|
||||||
|
Number: i + 1,
|
||||||
|
Status: model.StatusPending,
|
||||||
|
Environment: axis,
|
||||||
|
Enqueued: b.Curr.Created,
|
||||||
|
}
|
||||||
|
|
||||||
|
metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, job, b.Link)
|
||||||
|
environ := metadata.Environ()
|
||||||
|
for k, v := range metadata.EnvironDrone() {
|
||||||
|
environ[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
secrets := map[string]string{}
|
||||||
|
for _, sec := range b.Secs {
|
||||||
|
if !sec.MatchEvent(b.Curr.Event) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if b.Curr.Verified || sec.SkipVerify {
|
||||||
|
secrets[sec.Name] = sec.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sub := func(name string) string {
|
||||||
|
if v, ok := environ[name]; ok {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return secrets[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
y := b.Yaml
|
||||||
|
if s, err := envsubst.Eval(y, sub); err != nil {
|
||||||
|
y = s
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed, err := yaml.ParseString(y)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
metadata.Sys.Arch = parsed.Platform
|
||||||
|
if metadata.Sys.Arch == "" {
|
||||||
|
metadata.Sys.Arch = "linux/amd64"
|
||||||
|
}
|
||||||
|
|
||||||
|
lerr := linter.New(
|
||||||
|
linter.WithTrusted(b.Repo.IsTrusted),
|
||||||
|
).Lint(parsed)
|
||||||
|
if lerr != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ir := compiler.New(
|
||||||
|
compiler.WithEnviron(environ),
|
||||||
|
// TODO ability to customize the escalated plugins
|
||||||
|
compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
|
||||||
|
compiler.WithLocal(false),
|
||||||
|
compiler.WithNetrc(b.Netrc.Login, b.Netrc.Password, b.Netrc.Machine),
|
||||||
|
compiler.WithPrefix(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"%d_%d",
|
||||||
|
job.ID,
|
||||||
|
time.Now().Unix(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
compiler.WithEnviron(job.Environment),
|
||||||
|
compiler.WithProxy(),
|
||||||
|
// TODO ability to set global volumes for things like certs
|
||||||
|
compiler.WithVolumes(),
|
||||||
|
compiler.WithWorkspaceFromURL("/drone", b.Curr.Link),
|
||||||
|
).Compile(parsed)
|
||||||
|
|
||||||
|
for _, sec := range b.Secs {
|
||||||
|
if !sec.MatchEvent(b.Curr.Event) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if b.Curr.Verified || sec.SkipVerify {
|
||||||
|
ir.Secrets = append(ir.Secrets, &backend.Secret{
|
||||||
|
Mask: sec.Conceal,
|
||||||
|
Name: sec.Name,
|
||||||
|
Value: sec.Value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item := &buildItem{
|
||||||
|
Job: job,
|
||||||
|
Config: ir,
|
||||||
|
Labels: parsed.Labels,
|
||||||
|
Platform: metadata.Sys.Arch,
|
||||||
|
}
|
||||||
|
if item.Labels == nil {
|
||||||
|
item.Labels = map[string]string{}
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
func PostHook(c *gin.Context) {
|
func PostHook(c *gin.Context) {
|
||||||
remote_ := remote.FromContext(c)
|
remote_ := remote.FromContext(c)
|
||||||
|
|
||||||
tmprepo, build, err := remote_.Hook(c.Request)
|
tmprepo, build, err := remote_.Hook(c.Request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to parse hook. %s", err)
|
logrus.Errorf("failure to parse hook. %s", err)
|
||||||
c.AbortWithError(400, err)
|
c.AbortWithError(400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -31,7 +271,7 @@ func PostHook(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if tmprepo == nil {
|
if tmprepo == nil {
|
||||||
log.Errorf("failure to ascertain repo from hook.")
|
logrus.Errorf("failure to ascertain repo from hook.")
|
||||||
c.Writer.WriteHeader(400)
|
c.Writer.WriteHeader(400)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -40,14 +280,14 @@ func PostHook(c *gin.Context) {
|
|||||||
// wrapped in square brackets appear in the commit message
|
// wrapped in square brackets appear in the commit message
|
||||||
skipMatch := skipRe.FindString(build.Message)
|
skipMatch := skipRe.FindString(build.Message)
|
||||||
if len(skipMatch) > 0 {
|
if len(skipMatch) > 0 {
|
||||||
log.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
||||||
c.Writer.WriteHeader(204)
|
c.Writer.WriteHeader(204)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
||||||
c.AbortWithError(404, err)
|
c.AbortWithError(404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -57,18 +297,18 @@ func PostHook(c *gin.Context) {
|
|||||||
return repo.Hash, nil
|
return repo.Hash, nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(400, err)
|
c.AbortWithError(400, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if parsed.Text != repo.FullName {
|
if parsed.Text != repo.FullName {
|
||||||
log.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
||||||
c.AbortWithStatus(403)
|
c.AbortWithStatus(403)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if repo.UserID == 0 {
|
if repo.UserID == 0 {
|
||||||
log.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
||||||
c.Writer.WriteHeader(204)
|
c.Writer.WriteHeader(204)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -81,33 +321,18 @@ func PostHook(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if skipped {
|
if skipped {
|
||||||
log.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
||||||
c.Writer.WriteHeader(204)
|
c.Writer.WriteHeader(204)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := store.GetUser(c, repo.UserID)
|
user, err := store.GetUser(c, repo.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(500, err)
|
c.AbortWithError(500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// if there is no email address associated with the pull request,
|
|
||||||
// we lookup the email address based on the authors github login.
|
|
||||||
//
|
|
||||||
// my initial hesitation with this code is that it has the ability
|
|
||||||
// to expose your email address. At the same time, your email address
|
|
||||||
// is already exposed in the public .git log. So while some people will
|
|
||||||
// a small number of people will probably be upset by this, I'm not sure
|
|
||||||
// it is actually that big of a deal.
|
|
||||||
if len(build.Email) == 0 {
|
|
||||||
author, uerr := store.GetUserLogin(c, build.Author)
|
|
||||||
if uerr == nil {
|
|
||||||
build.Email = author.Email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the remote has a refresh token, the current access token
|
// if the remote has a refresh token, the current access token
|
||||||
// may be stale. Therefore, we should refresh prior to dispatching
|
// may be stale. Therefore, we should refresh prior to dispatching
|
||||||
// the job.
|
// the job.
|
||||||
@ -119,26 +344,16 @@ func PostHook(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fetch the build file from the database
|
// fetch the build file from the database
|
||||||
config := ToConfig(c)
|
cfg := ToConfig(c)
|
||||||
raw, err := remote_.File(user, repo, build, config.Yaml)
|
raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
||||||
c.AbortWithError(404, err)
|
c.AbortWithError(404, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
sec, err := remote_.File(user, repo, build, config.Shasum)
|
sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("cannot find build secrets for %s. %s", repo.FullName, err)
|
logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
|
||||||
// NOTE we don't exit on failure. The sec file is optional
|
|
||||||
}
|
|
||||||
|
|
||||||
axes, err := yaml.ParseMatrix(raw)
|
|
||||||
if err != nil {
|
|
||||||
c.String(500, "Failed to parse yaml file or calculate matrix. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(axes) == 0 {
|
|
||||||
axes = append(axes, yaml.Axis{})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
netrc, err := remote_.Netrc(user, repo)
|
netrc, err := remote_.Netrc(user, repo)
|
||||||
@ -148,24 +363,28 @@ func PostHook(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// verify the branches can be built vs skipped
|
// verify the branches can be built vs skipped
|
||||||
branches := yaml.ParseBranch(raw)
|
branches, err := yaml.ParseBytes(raw)
|
||||||
if !branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
if err != nil {
|
||||||
|
c.String(500, "Failed to parse yaml file. %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||||
c.String(200, "Branch does not match restrictions defined in yaml")
|
c.String(200, "Branch does not match restrictions defined in yaml")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
signature, err := jose.ParseSigned(string(sec))
|
signature, err := jose.ParseSigned(string(sec))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
||||||
} else if len(sec) == 0 {
|
} else if len(sec) == 0 {
|
||||||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||||
} else {
|
} else {
|
||||||
build.Signed = true
|
build.Signed = true
|
||||||
output, verr := signature.Verify([]byte(repo.Hash))
|
output, verr := signature.Verify([]byte(repo.Hash))
|
||||||
if verr != nil {
|
if verr != nil {
|
||||||
log.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
||||||
} else if string(output) != string(raw) {
|
} else if string(output) != string(raw) {
|
||||||
log.Debugf("cannot verify .drone.yml.sig file. no match")
|
logrus.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||||
} else {
|
} else {
|
||||||
build.Verified = true
|
build.Verified = true
|
||||||
}
|
}
|
||||||
@ -175,71 +394,94 @@ func PostHook(c *gin.Context) {
|
|||||||
build.Status = model.StatusPending
|
build.Status = model.StatusPending
|
||||||
build.RepoID = repo.ID
|
build.RepoID = repo.ID
|
||||||
|
|
||||||
// and use a transaction
|
if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
|
||||||
var jobs []*model.Job
|
logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||||
for num, axis := range axes {
|
|
||||||
jobs = append(jobs, &model.Job{
|
|
||||||
BuildID: build.ID,
|
|
||||||
Number: num + 1,
|
|
||||||
Status: model.StatusPending,
|
|
||||||
Environment: axis,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
err = store.CreateBuild(c, build, jobs...)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(500, err)
|
c.AbortWithError(500, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(200, build)
|
c.JSON(200, build)
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
|
||||||
err = remote_.Status(user, repo, build, url)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the previous build so that we can send
|
// get the previous build so that we can send
|
||||||
// on status change notifications
|
// on status change notifications
|
||||||
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
||||||
secs, err := store.GetMergedSecretList(c, repo)
|
secs, err := store.GetMergedSecretList(c, repo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
client := stomp.MustFromContext(c)
|
//
|
||||||
client.SendJSON("topic/events", model.Event{
|
// BELOW: NEW
|
||||||
|
//
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
||||||
|
err = remote_.Status(user, repo, build, uri)
|
||||||
|
if err != nil {
|
||||||
|
logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
b := builder{
|
||||||
|
Repo: repo,
|
||||||
|
Curr: build,
|
||||||
|
Last: last,
|
||||||
|
Netrc: netrc,
|
||||||
|
Secs: secs,
|
||||||
|
Link: httputil.GetURL(c.Request),
|
||||||
|
Yaml: string(raw),
|
||||||
|
}
|
||||||
|
items, err := b.Build()
|
||||||
|
if err != nil {
|
||||||
|
build.Status = model.StatusError
|
||||||
|
build.Started = time.Now().Unix()
|
||||||
|
build.Finished = build.Started
|
||||||
|
build.Error = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
build.Jobs = append(build.Jobs, item.Job)
|
||||||
|
store.CreateJob(c, item.Job)
|
||||||
|
// TODO err
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// publish topic
|
||||||
|
//
|
||||||
|
message := pubsub.Message{
|
||||||
|
Labels: map[string]string{
|
||||||
|
"repo": repo.FullName,
|
||||||
|
"private": strconv.FormatBool(repo.IsPrivate),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
message.Data, _ = json.Marshal(model.Event{
|
||||||
Type: model.Enqueued,
|
Type: model.Enqueued,
|
||||||
Repo: *repo,
|
Repo: *repo,
|
||||||
Build: *build,
|
Build: *build,
|
||||||
},
|
})
|
||||||
stomp.WithHeader("repo", repo.FullName),
|
// TODO remove global reference
|
||||||
stomp.WithHeader("private", strconv.FormatBool(repo.IsPrivate)),
|
config.pubsub.Publish(c, "topic/events", message)
|
||||||
)
|
//
|
||||||
|
// end publish topic
|
||||||
|
//
|
||||||
|
|
||||||
for _, job := range jobs {
|
for _, item := range items {
|
||||||
broker, _ := stomp.FromContext(c)
|
task := new(queue.Task)
|
||||||
broker.SendJSON("/queue/pending", &model.Work{
|
task.ID = fmt.Sprint(item.Job.ID)
|
||||||
Signed: build.Signed,
|
task.Labels = map[string]string{}
|
||||||
Verified: build.Verified,
|
task.Labels["platform"] = item.Platform
|
||||||
User: user,
|
for k, v := range item.Labels {
|
||||||
Repo: repo,
|
task.Labels[k] = v
|
||||||
Build: build,
|
}
|
||||||
BuildLast: last,
|
|
||||||
Job: job,
|
task.Data, _ = json.Marshal(rpc.Pipeline{
|
||||||
Netrc: netrc,
|
ID: fmt.Sprint(item.Job.ID),
|
||||||
Yaml: string(raw),
|
Config: item.Config,
|
||||||
Secrets: secs,
|
Timeout: b.Repo.Timeout,
|
||||||
System: &model.System{Link: httputil.GetURL(c.Request)},
|
})
|
||||||
},
|
|
||||||
stomp.WithHeader(
|
config.logger.Open(context.Background(), task.ID)
|
||||||
"platform",
|
config.queue.Push(context.Background(), task)
|
||||||
yaml.ParsePlatformDefault(raw, "linux/amd64"),
|
|
||||||
),
|
|
||||||
stomp.WithHeaders(
|
|
||||||
yaml.ParseLabel(raw),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
869
server/hook2.go
869
server/hook2.go
@ -1,869 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"github.com/square/go-jose"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/drone/drone/remote"
|
|
||||||
"github.com/drone/drone/shared/httputil"
|
|
||||||
"github.com/drone/drone/shared/token"
|
|
||||||
"github.com/drone/drone/store"
|
|
||||||
"github.com/drone/envsubst"
|
|
||||||
|
|
||||||
"github.com/cncd/pipeline/pipeline/backend"
|
|
||||||
"github.com/cncd/pipeline/pipeline/frontend"
|
|
||||||
"github.com/cncd/pipeline/pipeline/frontend/yaml"
|
|
||||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/compiler"
|
|
||||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/linter"
|
|
||||||
"github.com/cncd/pipeline/pipeline/frontend/yaml/matrix"
|
|
||||||
"github.com/cncd/pipeline/pipeline/rpc"
|
|
||||||
"github.com/cncd/pubsub"
|
|
||||||
"github.com/cncd/queue"
|
|
||||||
)
|
|
||||||
|
|
||||||
//
|
|
||||||
// CANARY IMPLEMENTATION
|
|
||||||
//
|
|
||||||
// This file is a complete disaster because I'm trying to wedge in some
|
|
||||||
// experimental code. Please pardon our appearance during renovations.
|
|
||||||
//
|
|
||||||
|
|
||||||
var skipRe = regexp.MustCompile(`\[(?i:ci *skip|skip *ci)\]`)
|
|
||||||
|
|
||||||
func GetQueueInfo(c *gin.Context) {
|
|
||||||
c.IndentedJSON(200,
|
|
||||||
config.queue.Info(c),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func PostHookOld(c *gin.Context) {
|
|
||||||
// remote_ := remote.FromContext(c)
|
|
||||||
//
|
|
||||||
// tmprepo, build, err := remote_.Hook(c.Request)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failure to parse hook. %s", err)
|
|
||||||
// c.AbortWithError(400, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if build == nil {
|
|
||||||
// c.Writer.WriteHeader(200)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if tmprepo == nil {
|
|
||||||
// logrus.Errorf("failure to ascertain repo from hook.")
|
|
||||||
// c.Writer.WriteHeader(400)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // skip the build if any case-insensitive combination of the words "skip" and "ci"
|
|
||||||
// // wrapped in square brackets appear in the commit message
|
|
||||||
// skipMatch := skipRe.FindString(build.Message)
|
|
||||||
// if len(skipMatch) > 0 {
|
|
||||||
// logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
|
||||||
// c.Writer.WriteHeader(204)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
|
||||||
// c.AbortWithError(404, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // get the token and verify the hook is authorized
|
|
||||||
// parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
|
||||||
// return repo.Hash, nil
|
|
||||||
// })
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
|
||||||
// c.AbortWithError(400, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if parsed.Text != repo.FullName {
|
|
||||||
// logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
|
||||||
// c.AbortWithStatus(403)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if repo.UserID == 0 {
|
|
||||||
// logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
|
||||||
// c.Writer.WriteHeader(204)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// var skipped = true
|
|
||||||
// if (build.Event == model.EventPush && repo.AllowPush) ||
|
|
||||||
// (build.Event == model.EventPull && repo.AllowPull) ||
|
|
||||||
// (build.Event == model.EventDeploy && repo.AllowDeploy) ||
|
|
||||||
// (build.Event == model.EventTag && repo.AllowTag) {
|
|
||||||
// skipped = false
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if skipped {
|
|
||||||
// logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
|
||||||
// c.Writer.WriteHeader(204)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// user, err := store.GetUser(c, repo.UserID)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
|
||||||
// c.AbortWithError(500, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // if the remote has a refresh token, the current access token
|
|
||||||
// // may be stale. Therefore, we should refresh prior to dispatching
|
|
||||||
// // the job.
|
|
||||||
// if refresher, ok := remote_.(remote.Refresher); ok {
|
|
||||||
// ok, _ := refresher.Refresh(user)
|
|
||||||
// if ok {
|
|
||||||
// store.UpdateUser(c, user)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // fetch the build file from the database
|
|
||||||
// cfg := ToConfig(c)
|
|
||||||
// raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
|
||||||
// c.AbortWithError(404, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
|
|
||||||
// // NOTE we don't exit on failure. The sec file is optional
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// axes, err := matrix.Parse(raw)
|
|
||||||
// if err != nil {
|
|
||||||
// c.String(500, "Failed to parse yaml file or calculate matrix. %s", err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if len(axes) == 0 {
|
|
||||||
// axes = append(axes, matrix.Axis{})
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// netrc, err := remote_.Netrc(user, repo)
|
|
||||||
// if err != nil {
|
|
||||||
// c.String(500, "Failed to generate netrc file. %s", err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // verify the branches can be built vs skipped
|
|
||||||
// branches, err := yaml.ParseBytes(raw)
|
|
||||||
// if err != nil {
|
|
||||||
// c.String(500, "Failed to parse yaml file. %s", err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
|
||||||
// c.String(200, "Branch does not match restrictions defined in yaml")
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// signature, err := jose.ParseSigned(string(sec))
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
|
||||||
// } else if len(sec) == 0 {
|
|
||||||
// logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
|
|
||||||
// } else {
|
|
||||||
// build.Signed = true
|
|
||||||
// output, verr := signature.Verify([]byte(repo.Hash))
|
|
||||||
// if verr != nil {
|
|
||||||
// logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
|
||||||
// } else if string(output) != string(raw) {
|
|
||||||
// logrus.Debugf("cannot verify .drone.yml.sig file. no match")
|
|
||||||
// } else {
|
|
||||||
// build.Verified = true
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // update some build fields
|
|
||||||
// build.Status = model.StatusPending
|
|
||||||
// build.RepoID = repo.ID
|
|
||||||
//
|
|
||||||
// // and use a transaction
|
|
||||||
// var jobs []*model.Job
|
|
||||||
// for num, axis := range axes {
|
|
||||||
// jobs = append(jobs, &model.Job{
|
|
||||||
// BuildID: build.ID,
|
|
||||||
// Number: num + 1,
|
|
||||||
// Status: model.StatusPending,
|
|
||||||
// Environment: axis,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// err = store.CreateBuild(c, build, jobs...)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
|
||||||
// c.AbortWithError(500, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// c.JSON(200, build)
|
|
||||||
//
|
|
||||||
// uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
|
||||||
// err = remote_.Status(user, repo, build, uri)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // get the previous build so that we can send
|
|
||||||
// // on status change notifications
|
|
||||||
// last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
|
||||||
// secs, err := store.GetMergedSecretList(c, repo)
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// //
|
|
||||||
// // BELOW: NEW
|
|
||||||
// //
|
|
||||||
//
|
|
||||||
// b := builder{
|
|
||||||
// Repo: repo,
|
|
||||||
// Curr: build,
|
|
||||||
// Last: last,
|
|
||||||
// Netrc: netrc,
|
|
||||||
// Secs: secs,
|
|
||||||
// Link: httputil.GetURL(c.Request),
|
|
||||||
// Yaml: string(raw),
|
|
||||||
// }
|
|
||||||
// items, err := b.Build()
|
|
||||||
// if err != nil {
|
|
||||||
// build.Status = model.StatusError
|
|
||||||
// build.Started = time.Now().Unix()
|
|
||||||
// build.Finished = build.Started
|
|
||||||
// build.Error = err.Error()
|
|
||||||
// store.CreateBuild(c, build, build.Jobs...)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for _, item := range items {
|
|
||||||
// build.Jobs = append(build.Jobs, item.Job)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
|
|
||||||
// logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
|
||||||
// c.AbortWithError(500, err)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for _, item := range items {
|
|
||||||
//
|
|
||||||
// task := new(queue.Task)
|
|
||||||
// task.ID = fmt.Sprint(item.Job.ID)
|
|
||||||
// task.Labels = map[string]string{}
|
|
||||||
// task.Labels["platform"] = item.Platform
|
|
||||||
// for k, v := range item.Labels {
|
|
||||||
// task.Labels[k] = v
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// task.Data, _ = json.Marshal(rpc.Pipeline{
|
|
||||||
// ID: fmt.Sprint(item.Job.ID),
|
|
||||||
// Config: item.Config,
|
|
||||||
// Timeout: b.Repo.Timeout,
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// config.logger.Open(context.Background(), task.ID)
|
|
||||||
// config.queue.Push(context.Background(), task)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// //
|
|
||||||
// // new code here
|
|
||||||
// //
|
|
||||||
//
|
|
||||||
// message := pubsub.Message{
|
|
||||||
// Labels: map[string]string{
|
|
||||||
// "repo": repo.FullName,
|
|
||||||
// "private": strconv.FormatBool(repo.IsPrivate),
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
// message.Data, _ = json.Marshal(model.Event{
|
|
||||||
// Type: model.Enqueued,
|
|
||||||
// Repo: *repo,
|
|
||||||
// Build: *build,
|
|
||||||
// })
|
|
||||||
// // TODO remove global reference
|
|
||||||
// config.pubsub.Publish(c, "topic/events", message)
|
|
||||||
//
|
|
||||||
// //
|
|
||||||
// // workspace
|
|
||||||
// //
|
|
||||||
//
|
|
||||||
// for _, job := range jobs {
|
|
||||||
//
|
|
||||||
// metadata := metadataFromStruct(repo, build, last, job, httputil.GetURL(c.Request))
|
|
||||||
// environ := metadata.Environ()
|
|
||||||
//
|
|
||||||
// secrets := map[string]string{}
|
|
||||||
// for _, sec := range secs {
|
|
||||||
// if !sec.MatchEvent(build.Event) {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// if build.Verified || sec.SkipVerify {
|
|
||||||
// secrets[sec.Name] = sec.Value
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// sub := func(name string) string {
|
|
||||||
// if v, ok := environ[name]; ok {
|
|
||||||
// return v
|
|
||||||
// }
|
|
||||||
// return secrets[name]
|
|
||||||
// }
|
|
||||||
// if s, err := envsubst.Eval(string(raw), sub); err != nil {
|
|
||||||
// raw = []byte(s)
|
|
||||||
// }
|
|
||||||
// parsed, err := yaml.ParseBytes(raw)
|
|
||||||
// if err != nil {
|
|
||||||
// job.ExitCode = 255
|
|
||||||
// job.Enqueued = time.Now().Unix()
|
|
||||||
// job.Started = time.Now().Unix()
|
|
||||||
// job.Finished = time.Now().Unix()
|
|
||||||
// job.Error = err.Error()
|
|
||||||
// store.UpdateBuildJob(c, build, job)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// lerr := linter.New(
|
|
||||||
// linter.WithTrusted(repo.IsTrusted),
|
|
||||||
// ).Lint(parsed)
|
|
||||||
// if lerr != nil {
|
|
||||||
// job.ExitCode = 255
|
|
||||||
// job.Enqueued = time.Now().Unix()
|
|
||||||
// job.Started = time.Now().Unix()
|
|
||||||
// job.Finished = time.Now().Unix()
|
|
||||||
// job.Error = lerr.Error()
|
|
||||||
// store.UpdateBuildJob(c, build, job)
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ir := compiler.New(
|
|
||||||
// compiler.WithEnviron(environ),
|
|
||||||
// // TODO ability to customize the escalated plugins
|
|
||||||
// compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
|
|
||||||
// compiler.WithLocal(false),
|
|
||||||
// compiler.WithNetrc(netrc.Login, netrc.Password, netrc.Machine),
|
|
||||||
// compiler.WithPrefix(
|
|
||||||
// fmt.Sprintf(
|
|
||||||
// "%d_%d",
|
|
||||||
// job.ID,
|
|
||||||
// time.Now().Unix(),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// compiler.WithEnviron(job.Environment),
|
|
||||||
// compiler.WithProxy(),
|
|
||||||
// // TODO ability to set global volumes for things like certs
|
|
||||||
// compiler.WithVolumes(),
|
|
||||||
// compiler.WithWorkspaceFromURL("/drone", repo.Link),
|
|
||||||
// ).Compile(parsed)
|
|
||||||
//
|
|
||||||
// // TODO there is a chicken and egg problem here because
|
|
||||||
// // the compiled yaml has a platform environment variable
|
|
||||||
// // that is not correctly set, because we are just about
|
|
||||||
// // to set it ....
|
|
||||||
// // TODO maybe we remove platform from metadata and let
|
|
||||||
// // the compiler set the value from the yaml itself.
|
|
||||||
// if parsed.Platform == "" {
|
|
||||||
// parsed.Platform = "linux/amd64"
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// for _, sec := range secs {
|
|
||||||
// if !sec.MatchEvent(build.Event) {
|
|
||||||
// continue
|
|
||||||
// }
|
|
||||||
// if build.Verified || sec.SkipVerify {
|
|
||||||
// ir.Secrets = append(ir.Secrets, &backend.Secret{
|
|
||||||
// Mask: sec.Conceal,
|
|
||||||
// Name: sec.Name,
|
|
||||||
// Value: sec.Value,
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// task := new(queue.Task)
|
|
||||||
// task.ID = fmt.Sprint(job.ID)
|
|
||||||
// task.Labels = map[string]string{}
|
|
||||||
// task.Labels["platform"] = parsed.Platform
|
|
||||||
// if parsed.Labels != nil {
|
|
||||||
// for k, v := range parsed.Labels {
|
|
||||||
// task.Labels[k] = v
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// task.Data, _ = json.Marshal(rpc.Pipeline{
|
|
||||||
// ID: fmt.Sprint(job.ID),
|
|
||||||
// Config: ir,
|
|
||||||
// Timeout: repo.Timeout,
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// config.logger.Open(context.Background(), task.ID)
|
|
||||||
// config.queue.Push(context.Background(), task)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return the metadata from the cli context.
|
|
||||||
func metadataFromStruct(repo *model.Repo, build, last *model.Build, job *model.Job, link string) frontend.Metadata {
|
|
||||||
return frontend.Metadata{
|
|
||||||
Repo: frontend.Repo{
|
|
||||||
Name: repo.Name,
|
|
||||||
Link: repo.Link,
|
|
||||||
Remote: repo.Clone,
|
|
||||||
Private: repo.IsPrivate,
|
|
||||||
},
|
|
||||||
Curr: frontend.Build{
|
|
||||||
Number: build.Number,
|
|
||||||
Created: build.Created,
|
|
||||||
Started: build.Started,
|
|
||||||
Finished: build.Finished,
|
|
||||||
Status: build.Status,
|
|
||||||
Event: build.Event,
|
|
||||||
Link: build.Link,
|
|
||||||
Target: build.Deploy,
|
|
||||||
Commit: frontend.Commit{
|
|
||||||
Sha: build.Commit,
|
|
||||||
Ref: build.Ref,
|
|
||||||
Refspec: build.Refspec,
|
|
||||||
Branch: build.Branch,
|
|
||||||
Message: build.Message,
|
|
||||||
Author: frontend.Author{
|
|
||||||
Name: build.Author,
|
|
||||||
Email: build.Email,
|
|
||||||
Avatar: build.Avatar,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Prev: frontend.Build{
|
|
||||||
Number: last.Number,
|
|
||||||
Created: last.Created,
|
|
||||||
Started: last.Started,
|
|
||||||
Finished: last.Finished,
|
|
||||||
Status: last.Status,
|
|
||||||
Event: last.Event,
|
|
||||||
Link: last.Link,
|
|
||||||
Target: last.Deploy,
|
|
||||||
Commit: frontend.Commit{
|
|
||||||
Sha: last.Commit,
|
|
||||||
Ref: last.Ref,
|
|
||||||
Refspec: last.Refspec,
|
|
||||||
Branch: last.Branch,
|
|
||||||
Message: last.Message,
|
|
||||||
Author: frontend.Author{
|
|
||||||
Name: last.Author,
|
|
||||||
Email: last.Email,
|
|
||||||
Avatar: last.Avatar,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Job: frontend.Job{
|
|
||||||
Number: job.Number,
|
|
||||||
Matrix: job.Environment,
|
|
||||||
},
|
|
||||||
Sys: frontend.System{
|
|
||||||
Name: "drone",
|
|
||||||
Link: link,
|
|
||||||
Arch: "linux/amd64",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// use helper funciton to return ([]backend.Config, error)
|
|
||||||
|
|
||||||
// 1. fetch everything from github
|
|
||||||
// 2. create and persist the build object
|
|
||||||
//
|
|
||||||
// 3. generate the build jobs [Launcher?]
|
|
||||||
// a. parse yaml
|
|
||||||
// b. lint yaml
|
|
||||||
// c. compile yaml
|
|
||||||
//
|
|
||||||
// 4. persist the build jobs (... what if I already have jobs, via re-start)
|
|
||||||
// 5. update github status
|
|
||||||
// 6. send to queue
|
|
||||||
// 7. trigger pubsub
|
|
||||||
|
|
||||||
type builder struct {
|
|
||||||
Repo *model.Repo
|
|
||||||
Curr *model.Build
|
|
||||||
Last *model.Build
|
|
||||||
Netrc *model.Netrc
|
|
||||||
Secs []*model.Secret
|
|
||||||
Link string
|
|
||||||
Yaml string
|
|
||||||
}
|
|
||||||
|
|
||||||
type buildItem struct {
|
|
||||||
Job *model.Job
|
|
||||||
Platform string
|
|
||||||
Labels map[string]string
|
|
||||||
Config *backend.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *builder) Build() ([]*buildItem, error) {
|
|
||||||
|
|
||||||
axes, err := matrix.ParseString(b.Yaml)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(axes) == 0 {
|
|
||||||
axes = append(axes, matrix.Axis{})
|
|
||||||
}
|
|
||||||
|
|
||||||
var items []*buildItem
|
|
||||||
for i, axis := range axes {
|
|
||||||
job := &model.Job{
|
|
||||||
BuildID: b.Curr.ID,
|
|
||||||
Number: i + 1,
|
|
||||||
Status: model.StatusPending,
|
|
||||||
Environment: axis,
|
|
||||||
Enqueued: b.Curr.Created,
|
|
||||||
}
|
|
||||||
|
|
||||||
metadata := metadataFromStruct(b.Repo, b.Curr, b.Last, job, b.Link)
|
|
||||||
environ := metadata.Environ()
|
|
||||||
for k, v := range metadata.EnvironDrone() {
|
|
||||||
environ[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
secrets := map[string]string{}
|
|
||||||
for _, sec := range b.Secs {
|
|
||||||
if !sec.MatchEvent(b.Curr.Event) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if b.Curr.Verified || sec.SkipVerify {
|
|
||||||
secrets[sec.Name] = sec.Value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sub := func(name string) string {
|
|
||||||
if v, ok := environ[name]; ok {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return secrets[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
y := b.Yaml
|
|
||||||
if s, err := envsubst.Eval(y, sub); err != nil {
|
|
||||||
y = s
|
|
||||||
}
|
|
||||||
|
|
||||||
parsed, err := yaml.ParseString(y)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
metadata.Sys.Arch = parsed.Platform
|
|
||||||
if metadata.Sys.Arch == "" {
|
|
||||||
metadata.Sys.Arch = "linux/amd64"
|
|
||||||
}
|
|
||||||
|
|
||||||
lerr := linter.New(
|
|
||||||
linter.WithTrusted(b.Repo.IsTrusted),
|
|
||||||
).Lint(parsed)
|
|
||||||
if lerr != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
ir := compiler.New(
|
|
||||||
compiler.WithEnviron(environ),
|
|
||||||
// TODO ability to customize the escalated plugins
|
|
||||||
compiler.WithEscalated("plugins/docker", "plugins/gcr", "plugins/ecr"),
|
|
||||||
compiler.WithLocal(false),
|
|
||||||
compiler.WithNetrc(b.Netrc.Login, b.Netrc.Password, b.Netrc.Machine),
|
|
||||||
compiler.WithPrefix(
|
|
||||||
fmt.Sprintf(
|
|
||||||
"%d_%d",
|
|
||||||
job.ID,
|
|
||||||
time.Now().Unix(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
compiler.WithEnviron(job.Environment),
|
|
||||||
compiler.WithProxy(),
|
|
||||||
// TODO ability to set global volumes for things like certs
|
|
||||||
compiler.WithVolumes(),
|
|
||||||
compiler.WithWorkspaceFromURL("/drone", b.Curr.Link),
|
|
||||||
).Compile(parsed)
|
|
||||||
|
|
||||||
for _, sec := range b.Secs {
|
|
||||||
if !sec.MatchEvent(b.Curr.Event) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if b.Curr.Verified || sec.SkipVerify {
|
|
||||||
ir.Secrets = append(ir.Secrets, &backend.Secret{
|
|
||||||
Mask: sec.Conceal,
|
|
||||||
Name: sec.Name,
|
|
||||||
Value: sec.Value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item := &buildItem{
|
|
||||||
Job: job,
|
|
||||||
Config: ir,
|
|
||||||
Labels: parsed.Labels,
|
|
||||||
Platform: metadata.Sys.Arch,
|
|
||||||
}
|
|
||||||
if item.Labels == nil {
|
|
||||||
item.Labels = map[string]string{}
|
|
||||||
}
|
|
||||||
items = append(items, item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return items, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
//
|
|
||||||
|
|
||||||
func PostHook2(c *gin.Context) {
|
|
||||||
remote_ := remote.FromContext(c)
|
|
||||||
|
|
||||||
tmprepo, build, err := remote_.Hook(c.Request)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failure to parse hook. %s", err)
|
|
||||||
c.AbortWithError(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if build == nil {
|
|
||||||
c.Writer.WriteHeader(200)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if tmprepo == nil {
|
|
||||||
logrus.Errorf("failure to ascertain repo from hook.")
|
|
||||||
c.Writer.WriteHeader(400)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip the build if any case-insensitive combination of the words "skip" and "ci"
|
|
||||||
// wrapped in square brackets appear in the commit message
|
|
||||||
skipMatch := skipRe.FindString(build.Message)
|
|
||||||
if len(skipMatch) > 0 {
|
|
||||||
logrus.Infof("ignoring hook. %s found in %s", skipMatch, build.Commit)
|
|
||||||
c.Writer.WriteHeader(204)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failure to find repo %s/%s from hook. %s", tmprepo.Owner, tmprepo.Name, err)
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the token and verify the hook is authorized
|
|
||||||
parsed, err := token.ParseRequest(c.Request, func(t *token.Token) (string, error) {
|
|
||||||
return repo.Hash, nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failure to parse token from hook for %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(400, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if parsed.Text != repo.FullName {
|
|
||||||
logrus.Errorf("failure to verify token from hook. Expected %s, got %s", repo.FullName, parsed.Text)
|
|
||||||
c.AbortWithStatus(403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if repo.UserID == 0 {
|
|
||||||
logrus.Warnf("ignoring hook. repo %s has no owner.", repo.FullName)
|
|
||||||
c.Writer.WriteHeader(204)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var skipped = true
|
|
||||||
if (build.Event == model.EventPush && repo.AllowPush) ||
|
|
||||||
(build.Event == model.EventPull && repo.AllowPull) ||
|
|
||||||
(build.Event == model.EventDeploy && repo.AllowDeploy) ||
|
|
||||||
(build.Event == model.EventTag && repo.AllowTag) {
|
|
||||||
skipped = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if skipped {
|
|
||||||
logrus.Infof("ignoring hook. repo %s is disabled for %s events.", repo.FullName, build.Event)
|
|
||||||
c.Writer.WriteHeader(204)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
user, err := store.GetUser(c, repo.UserID)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failure to find repo owner %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the remote has a refresh token, the current access token
|
|
||||||
// may be stale. Therefore, we should refresh prior to dispatching
|
|
||||||
// the job.
|
|
||||||
if refresher, ok := remote_.(remote.Refresher); ok {
|
|
||||||
ok, _ := refresher.Refresh(user)
|
|
||||||
if ok {
|
|
||||||
store.UpdateUser(c, user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// fetch the build file from the database
|
|
||||||
cfg := ToConfig(c)
|
|
||||||
raw, err := remote_.File(user, repo, build, cfg.Yaml)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("failure to get build config for %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
netrc, err := remote_.Netrc(user, repo)
|
|
||||||
if err != nil {
|
|
||||||
c.String(500, "Failed to generate netrc file. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify the branches can be built vs skipped
|
|
||||||
branches, err := yaml.ParseBytes(raw)
|
|
||||||
if err != nil {
|
|
||||||
c.String(500, "Failed to parse yaml file. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !branches.Branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
|
||||||
c.String(200, "Branch does not match restrictions defined in yaml")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
signature, err := jose.ParseSigned(string(sec))
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("cannot parse .drone.yml.sig file. %s", err)
|
|
||||||
} else if len(sec) == 0 {
|
|
||||||
logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
|
|
||||||
} else {
|
|
||||||
build.Signed = true
|
|
||||||
output, verr := signature.Verify([]byte(repo.Hash))
|
|
||||||
if verr != nil {
|
|
||||||
logrus.Debugf("cannot verify .drone.yml.sig file. %s", verr)
|
|
||||||
} else if string(output) != string(raw) {
|
|
||||||
logrus.Debugf("cannot verify .drone.yml.sig file. no match")
|
|
||||||
} else {
|
|
||||||
build.Verified = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// update some build fields
|
|
||||||
build.Status = model.StatusPending
|
|
||||||
build.RepoID = repo.ID
|
|
||||||
|
|
||||||
if err := store.CreateBuild(c, build, build.Jobs...); err != nil {
|
|
||||||
logrus.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
|
||||||
c.AbortWithError(500, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, build)
|
|
||||||
|
|
||||||
// get the previous build so that we can send
|
|
||||||
// on status change notifications
|
|
||||||
last, _ := store.GetBuildLastBefore(c, repo, build.Branch, build.ID)
|
|
||||||
secs, err := store.GetMergedSecretList(c, repo)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugf("Error getting secrets for %s#%d. %s", repo.FullName, build.Number, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// BELOW: NEW
|
|
||||||
//
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
uri := fmt.Sprintf("%s/%s/%d", httputil.GetURL(c.Request), repo.FullName, build.Number)
|
|
||||||
err = remote_.Status(user, repo, build, uri)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("error setting commit status for %s/%d", repo.FullName, build.Number)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
b := builder{
|
|
||||||
Repo: repo,
|
|
||||||
Curr: build,
|
|
||||||
Last: last,
|
|
||||||
Netrc: netrc,
|
|
||||||
Secs: secs,
|
|
||||||
Link: httputil.GetURL(c.Request),
|
|
||||||
Yaml: string(raw),
|
|
||||||
}
|
|
||||||
items, err := b.Build()
|
|
||||||
if err != nil {
|
|
||||||
build.Status = model.StatusError
|
|
||||||
build.Started = time.Now().Unix()
|
|
||||||
build.Finished = build.Started
|
|
||||||
build.Error = err.Error()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
build.Jobs = append(build.Jobs, item.Job)
|
|
||||||
store.CreateJob(c, item.Job)
|
|
||||||
// TODO err
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// publish topic
|
|
||||||
//
|
|
||||||
message := pubsub.Message{
|
|
||||||
Labels: map[string]string{
|
|
||||||
"repo": repo.FullName,
|
|
||||||
"private": strconv.FormatBool(repo.IsPrivate),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
message.Data, _ = json.Marshal(model.Event{
|
|
||||||
Type: model.Enqueued,
|
|
||||||
Repo: *repo,
|
|
||||||
Build: *build,
|
|
||||||
})
|
|
||||||
// TODO remove global reference
|
|
||||||
config.pubsub.Publish(c, "topic/events", message)
|
|
||||||
//
|
|
||||||
// end publish topic
|
|
||||||
//
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
task := new(queue.Task)
|
|
||||||
task.ID = fmt.Sprint(item.Job.ID)
|
|
||||||
task.Labels = map[string]string{}
|
|
||||||
task.Labels["platform"] = item.Platform
|
|
||||||
for k, v := range item.Labels {
|
|
||||||
task.Labels[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
task.Data, _ = json.Marshal(rpc.Pipeline{
|
|
||||||
ID: fmt.Sprint(item.Job.ID),
|
|
||||||
Config: item.Config,
|
|
||||||
Timeout: b.Repo.Timeout,
|
|
||||||
})
|
|
||||||
|
|
||||||
config.logger.Open(context.Background(), task.ID)
|
|
||||||
config.queue.Push(context.Background(), task)
|
|
||||||
}
|
|
||||||
}
|
|
163
server/queue.go
163
server/queue.go
@ -1,163 +0,0 @@
|
|||||||
package server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"golang.org/x/net/context"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/drone/drone/model"
|
|
||||||
"github.com/drone/drone/remote"
|
|
||||||
"github.com/drone/drone/store"
|
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
)
|
|
||||||
|
|
||||||
// newline defines a newline constant to separate lines in the build output
|
|
||||||
var newline = []byte{'\n'}
|
|
||||||
|
|
||||||
// upgrader defines the default behavior for upgrading the websocket.
|
|
||||||
var upgrader = websocket.Upgrader{
|
|
||||||
ReadBufferSize: 1024,
|
|
||||||
WriteBufferSize: 1024,
|
|
||||||
CheckOrigin: func(r *http.Request) bool {
|
|
||||||
return true
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleUpdate handles build updates from the agent and persists to the database.
|
|
||||||
func HandleUpdate(c context.Context, message *stomp.Message) {
|
|
||||||
defer func() {
|
|
||||||
message.Release()
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err := r.(error)
|
|
||||||
logrus.Errorf("Panic recover: broker update handler: %s", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
work := new(model.Work)
|
|
||||||
if err := message.Unmarshal(work); err != nil {
|
|
||||||
logrus.Errorf("Invalid input. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(bradrydzewski) it is really annoying that we have to do this lookup
|
|
||||||
// and I'd prefer not to. The reason we do this is because the Build and Job
|
|
||||||
// have fields that aren't serialized to json and would be reset to their
|
|
||||||
// empty values if we just saved what was coming in the http.Request body.
|
|
||||||
build, err := store.GetBuild(c, work.Build.ID)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Unable to find build. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
job, err := store.GetJob(c, work.Job.ID)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Unable to find job. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
build.Started = work.Build.Started
|
|
||||||
build.Finished = work.Build.Finished
|
|
||||||
build.Status = work.Build.Status
|
|
||||||
job.Started = work.Job.Started
|
|
||||||
job.Finished = work.Job.Finished
|
|
||||||
job.Status = work.Job.Status
|
|
||||||
job.ExitCode = work.Job.ExitCode
|
|
||||||
job.Error = work.Job.Error
|
|
||||||
|
|
||||||
if build.Status == model.StatusPending {
|
|
||||||
build.Started = work.Job.Started
|
|
||||||
build.Status = model.StatusRunning
|
|
||||||
store.UpdateBuild(c, build)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if job.Status == model.StatusRunning {
|
|
||||||
// err := stream.Create(c, stream.ToKey(job.ID))
|
|
||||||
// if err != nil {
|
|
||||||
// logrus.Errorf("Unable to create stream. %s", err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
ok, err := store.UpdateBuildJob(c, build, job)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Unable to update job. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok {
|
|
||||||
// get the user because we transfer the user form the server to agent
|
|
||||||
// and back we lose the token which does not get serialized to json.
|
|
||||||
user, uerr := store.GetUser(c, work.User.ID)
|
|
||||||
if uerr != nil {
|
|
||||||
logrus.Errorf("Unable to find user. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
remote.Status(c, user, work.Repo, build,
|
|
||||||
fmt.Sprintf("%s/%s/%d", work.System.Link, work.Repo.FullName, work.Build.Number))
|
|
||||||
}
|
|
||||||
|
|
||||||
client := stomp.MustFromContext(c)
|
|
||||||
err = client.SendJSON("/topic/events", model.Event{
|
|
||||||
Type: func() model.EventType {
|
|
||||||
// HACK we don't even really care about the event type.
|
|
||||||
// so we should just simplify how events are triggered.
|
|
||||||
if job.Status == model.StatusRunning {
|
|
||||||
return model.Started
|
|
||||||
}
|
|
||||||
return model.Finished
|
|
||||||
}(),
|
|
||||||
Repo: *work.Repo,
|
|
||||||
Build: *build,
|
|
||||||
Job: *job,
|
|
||||||
},
|
|
||||||
stomp.WithHeader("repo", work.Repo.FullName),
|
|
||||||
stomp.WithHeader("private", strconv.FormatBool(work.Repo.IsPrivate)),
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Unable to publish to /topic/events. %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if job.Status == model.StatusRunning {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
var sub []byte
|
|
||||||
|
|
||||||
done := make(chan bool)
|
|
||||||
dest := fmt.Sprintf("/topic/logs.%d", job.ID)
|
|
||||||
sub, err = client.Subscribe(dest, stomp.HandlerFunc(func(m *stomp.Message) {
|
|
||||||
defer m.Release()
|
|
||||||
if m.Header.GetBool("eof") {
|
|
||||||
done <- true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf.Write(m.Body)
|
|
||||||
buf.WriteByte('\n')
|
|
||||||
}))
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Unable to read logs from broker. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
client.Send(dest, []byte{}, stomp.WithRetain("remove"))
|
|
||||||
client.Unsubscribe(sub)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-done:
|
|
||||||
case <-time.After(30 * time.Second):
|
|
||||||
logrus.Errorf("Unable to read logs from broker. Timeout. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := store.WriteLog(c, job, &buf); err != nil {
|
|
||||||
logrus.Errorf("Unable to write logs to store. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
174
server/stream.go
174
server/stream.go
@ -3,6 +3,7 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -12,7 +13,6 @@ import (
|
|||||||
"github.com/drone/drone/model"
|
"github.com/drone/drone/model"
|
||||||
"github.com/drone/drone/router/middleware/session"
|
"github.com/drone/drone/router/middleware/session"
|
||||||
"github.com/drone/drone/store"
|
"github.com/drone/drone/store"
|
||||||
"github.com/drone/mq/stomp"
|
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@ -28,160 +28,17 @@ var (
|
|||||||
|
|
||||||
// Send pings to client with this period. Must be less than pongWait.
|
// Send pings to client with this period. Must be less than pongWait.
|
||||||
pingPeriod = 30 * time.Second
|
pingPeriod = 30 * time.Second
|
||||||
|
|
||||||
|
// upgrader defines the default behavior for upgrading the websocket.
|
||||||
|
upgrader = websocket.Upgrader{
|
||||||
|
ReadBufferSize: 1024,
|
||||||
|
WriteBufferSize: 1024,
|
||||||
|
CheckOrigin: func(r *http.Request) bool {
|
||||||
|
return true
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// LogStream streams the build log output to the client.
|
|
||||||
func LogStream(c *gin.Context) {
|
|
||||||
repo := session.Repo(c)
|
|
||||||
buildn, _ := strconv.Atoi(c.Param("build"))
|
|
||||||
jobn, _ := strconv.Atoi(c.Param("number"))
|
|
||||||
|
|
||||||
c.Writer.Header().Set("Content-Type", "text/event-stream")
|
|
||||||
|
|
||||||
build, err := store.GetBuildNumber(c, repo, buildn)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugln("stream cannot get build number.", err)
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
job, err := store.GetJobNumber(c, build, jobn)
|
|
||||||
if err != nil {
|
|
||||||
logrus.Debugln("stream cannot get job number.", err)
|
|
||||||
c.AbortWithError(404, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if job.Status != model.StatusRunning {
|
|
||||||
logrus.Debugln("stream not found.")
|
|
||||||
c.AbortWithStatus(404)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
|
||||||
logrus.Errorf("Cannot upgrade websocket. %s", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logrus.Debugf("Successfull upgraded websocket")
|
|
||||||
|
|
||||||
ticker := time.NewTicker(pingPeriod)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
logs := make(chan []byte)
|
|
||||||
done := make(chan bool)
|
|
||||||
var eof bool
|
|
||||||
dest := fmt.Sprintf("/topic/logs.%d", job.ID)
|
|
||||||
client, _ := stomp.FromContext(c)
|
|
||||||
sub, err := client.Subscribe(dest, stomp.HandlerFunc(func(m *stomp.Message) {
|
|
||||||
if m.Header.GetBool("eof") {
|
|
||||||
eof = true
|
|
||||||
done <- true
|
|
||||||
} else if eof {
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
logs <- m.Body
|
|
||||||
}
|
|
||||||
m.Release()
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Unable to read logs from broker. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
client.Unsubscribe(sub)
|
|
||||||
close(done)
|
|
||||||
close(logs)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case buf := <-logs:
|
|
||||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
|
||||||
ws.WriteMessage(websocket.TextMessage, buf)
|
|
||||||
case <-done:
|
|
||||||
return
|
|
||||||
case <-ticker.C:
|
|
||||||
err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EventStream produces the User event stream, sending all repository, build
|
|
||||||
// and agent events to the client.
|
|
||||||
func EventStream(c *gin.Context) {
|
|
||||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
|
||||||
logrus.Errorf("Cannot upgrade websocket. %s", err)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logrus.Debugf("Successfull upgraded websocket")
|
|
||||||
|
|
||||||
user := session.User(c)
|
|
||||||
repo := map[string]bool{}
|
|
||||||
if user != nil {
|
|
||||||
repo, _ = cache.GetRepoMap(c, user)
|
|
||||||
}
|
|
||||||
|
|
||||||
eventc := make(chan []byte, 10)
|
|
||||||
quitc := make(chan bool)
|
|
||||||
tick := time.NewTicker(pingPeriod)
|
|
||||||
defer func() {
|
|
||||||
tick.Stop()
|
|
||||||
ws.Close()
|
|
||||||
logrus.Debug("Successfully closed websocket")
|
|
||||||
}()
|
|
||||||
|
|
||||||
client := stomp.MustFromContext(c)
|
|
||||||
sub, err := client.Subscribe("/topic/events", stomp.HandlerFunc(func(m *stomp.Message) {
|
|
||||||
name := m.Header.GetString("repo")
|
|
||||||
priv := m.Header.GetBool("private")
|
|
||||||
if repo[name] || !priv {
|
|
||||||
eventc <- m.Body
|
|
||||||
}
|
|
||||||
m.Release()
|
|
||||||
}))
|
|
||||||
if err != nil {
|
|
||||||
logrus.Errorf("Unable to read logs from broker. %s", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
client.Unsubscribe(sub)
|
|
||||||
close(quitc)
|
|
||||||
close(eventc)
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
recover()
|
|
||||||
}()
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-quitc:
|
|
||||||
return
|
|
||||||
case event, ok := <-eventc:
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
|
||||||
ws.WriteMessage(websocket.TextMessage, event)
|
|
||||||
case <-tick.C:
|
|
||||||
err := ws.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(writeWait))
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
reader(ws)
|
|
||||||
}
|
|
||||||
|
|
||||||
func reader(ws *websocket.Conn) {
|
func reader(ws *websocket.Conn) {
|
||||||
defer ws.Close()
|
defer ws.Close()
|
||||||
ws.SetReadLimit(512)
|
ws.SetReadLimit(512)
|
||||||
@ -198,14 +55,7 @@ func reader(ws *websocket.Conn) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
func LogStream(c *gin.Context) {
|
||||||
// CANARY IMPLEMENTATION
|
|
||||||
//
|
|
||||||
// This file is a complete disaster because I'm trying to wedge in some
|
|
||||||
// experimental code. Please pardon our appearance during renovations.
|
|
||||||
//
|
|
||||||
|
|
||||||
func LogStream2(c *gin.Context) {
|
|
||||||
repo := session.Repo(c)
|
repo := session.Repo(c)
|
||||||
buildn, _ := strconv.Atoi(c.Param("build"))
|
buildn, _ := strconv.Atoi(c.Param("build"))
|
||||||
jobn, _ := strconv.Atoi(c.Param("number"))
|
jobn, _ := strconv.Atoi(c.Param("number"))
|
||||||
@ -286,7 +136,7 @@ func LogStream2(c *gin.Context) {
|
|||||||
reader(ws)
|
reader(ws)
|
||||||
}
|
}
|
||||||
|
|
||||||
func EventStream2(c *gin.Context) {
|
func EventStream(c *gin.Context) {
|
||||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||||
|
27
vendor/code.google.com/p/go.crypto/LICENSE
generated
vendored
27
vendor/code.google.com/p/go.crypto/LICENSE
generated
vendored
@ -1,27 +0,0 @@
|
|||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
22
vendor/code.google.com/p/go.crypto/PATENTS
generated
vendored
22
vendor/code.google.com/p/go.crypto/PATENTS
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
||||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
||||||
patent license to make, have made, use, offer to sell, sell, import,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
250
vendor/code.google.com/p/go.crypto/ssh/agent.go
generated
vendored
250
vendor/code.google.com/p/go.crypto/ssh/agent.go
generated
vendored
@ -1,250 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/base64"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 3.
|
|
||||||
const (
|
|
||||||
// 3.2 Requests from client to agent for protocol 2 key operations
|
|
||||||
agentRequestIdentities = 11
|
|
||||||
agentSignRequest = 13
|
|
||||||
agentAddIdentity = 17
|
|
||||||
agentRemoveIdentity = 18
|
|
||||||
agentRemoveAllIdentities = 19
|
|
||||||
agentAddIdConstrained = 25
|
|
||||||
|
|
||||||
// 3.3 Key-type independent requests from client to agent
|
|
||||||
agentAddSmartcardKey = 20
|
|
||||||
agentRemoveSmartcardKey = 21
|
|
||||||
agentLock = 22
|
|
||||||
agentUnlock = 23
|
|
||||||
agentAddSmartcardKeyConstrained = 26
|
|
||||||
|
|
||||||
// 3.4 Generic replies from agent to client
|
|
||||||
agentFailure = 5
|
|
||||||
agentSuccess = 6
|
|
||||||
|
|
||||||
// 3.6 Replies from agent to client for protocol 2 key operations
|
|
||||||
agentIdentitiesAnswer = 12
|
|
||||||
agentSignResponse = 14
|
|
||||||
|
|
||||||
// 3.7 Key constraint identifiers
|
|
||||||
agentConstrainLifetime = 1
|
|
||||||
agentConstrainConfirm = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
// maxAgentResponseBytes is the maximum agent reply size that is accepted. This
|
|
||||||
// is a sanity check, not a limit in the spec.
|
|
||||||
const maxAgentResponseBytes = 16 << 20
|
|
||||||
|
|
||||||
// Agent messages:
|
|
||||||
// These structures mirror the wire format of the corresponding ssh agent
|
|
||||||
// messages found in [PROTOCOL.agent].
|
|
||||||
|
|
||||||
type failureAgentMsg struct{}
|
|
||||||
|
|
||||||
type successAgentMsg struct{}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.5.2.
|
|
||||||
type requestIdentitiesAgentMsg struct{}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.5.2.
|
|
||||||
type identitiesAnswerAgentMsg struct {
|
|
||||||
NumKeys uint32
|
|
||||||
Keys []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.6.2.
|
|
||||||
type signRequestAgentMsg struct {
|
|
||||||
KeyBlob []byte
|
|
||||||
Data []byte
|
|
||||||
Flags uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See [PROTOCOL.agent], section 2.6.2.
|
|
||||||
type signResponseAgentMsg struct {
|
|
||||||
SigBlob []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentKey represents a protocol 2 key as defined in [PROTOCOL.agent],
|
|
||||||
// section 2.5.2.
|
|
||||||
type AgentKey struct {
|
|
||||||
blob []byte
|
|
||||||
Comment string
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns the storage form of an agent key with the format, base64
|
|
||||||
// encoded serialized key, and the comment if it is not empty.
|
|
||||||
func (ak *AgentKey) String() string {
|
|
||||||
algo, _, ok := parseString(ak.blob)
|
|
||||||
if !ok {
|
|
||||||
return "ssh: malformed key"
|
|
||||||
}
|
|
||||||
|
|
||||||
s := string(algo) + " " + base64.StdEncoding.EncodeToString(ak.blob)
|
|
||||||
|
|
||||||
if ak.Comment != "" {
|
|
||||||
s += " " + ak.Comment
|
|
||||||
}
|
|
||||||
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key returns an agent's public key as one of the supported key or certificate types.
|
|
||||||
func (ak *AgentKey) Key() (PublicKey, error) {
|
|
||||||
if key, _, ok := ParsePublicKey(ak.blob); ok {
|
|
||||||
return key, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("ssh: failed to parse key blob")
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseAgentKey(in []byte) (out *AgentKey, rest []byte, ok bool) {
|
|
||||||
ak := new(AgentKey)
|
|
||||||
|
|
||||||
if ak.blob, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
comment, in, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ak.Comment = string(comment)
|
|
||||||
|
|
||||||
return ak, in, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentClient provides a means to communicate with an ssh agent process based
|
|
||||||
// on the protocol described in [PROTOCOL.agent]?rev=1.6.
|
|
||||||
type AgentClient struct {
|
|
||||||
// conn is typically represented by using a *net.UnixConn
|
|
||||||
conn io.ReadWriter
|
|
||||||
// mu is used to prevent concurrent access to the agent
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewAgentClient creates and returns a new *AgentClient using the
|
|
||||||
// passed in io.ReadWriter as a connection to a ssh agent.
|
|
||||||
func NewAgentClient(rw io.ReadWriter) *AgentClient {
|
|
||||||
return &AgentClient{conn: rw}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendAndReceive sends req to the agent and waits for a reply. On success,
|
|
||||||
// the reply is unmarshaled into reply and replyType is set to the first byte of
|
|
||||||
// the reply, which contains the type of the message.
|
|
||||||
func (ac *AgentClient) sendAndReceive(req []byte) (reply interface{}, replyType uint8, err error) {
|
|
||||||
// ac.mu prevents multiple, concurrent requests. Since the agent is typically
|
|
||||||
// on the same machine, we don't attempt to pipeline the requests.
|
|
||||||
ac.mu.Lock()
|
|
||||||
defer ac.mu.Unlock()
|
|
||||||
|
|
||||||
msg := make([]byte, stringLength(len(req)))
|
|
||||||
marshalString(msg, req)
|
|
||||||
if _, err = ac.conn.Write(msg); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var respSizeBuf [4]byte
|
|
||||||
if _, err = io.ReadFull(ac.conn, respSizeBuf[:]); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
respSize, _, _ := parseUint32(respSizeBuf[:])
|
|
||||||
|
|
||||||
if respSize > maxAgentResponseBytes {
|
|
||||||
err = errors.New("ssh: agent reply too large")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := make([]byte, respSize)
|
|
||||||
if _, err = io.ReadFull(ac.conn, buf); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return unmarshalAgentMsg(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestIdentities queries the agent for protocol 2 keys as defined in
|
|
||||||
// [PROTOCOL.agent] section 2.5.2.
|
|
||||||
func (ac *AgentClient) RequestIdentities() ([]*AgentKey, error) {
|
|
||||||
req := marshal(agentRequestIdentities, requestIdentitiesAgentMsg{})
|
|
||||||
|
|
||||||
msg, msgType, err := ac.sendAndReceive(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *identitiesAnswerAgentMsg:
|
|
||||||
if msg.NumKeys > maxAgentResponseBytes/8 {
|
|
||||||
return nil, errors.New("ssh: too many keys in agent reply")
|
|
||||||
}
|
|
||||||
keys := make([]*AgentKey, msg.NumKeys)
|
|
||||||
data := msg.Keys
|
|
||||||
for i := uint32(0); i < msg.NumKeys; i++ {
|
|
||||||
var key *AgentKey
|
|
||||||
var ok bool
|
|
||||||
if key, data, ok = parseAgentKey(data); !ok {
|
|
||||||
return nil, ParseError{agentIdentitiesAnswer}
|
|
||||||
}
|
|
||||||
keys[i] = key
|
|
||||||
}
|
|
||||||
return keys, nil
|
|
||||||
case *failureAgentMsg:
|
|
||||||
return nil, errors.New("ssh: failed to list keys")
|
|
||||||
}
|
|
||||||
return nil, UnexpectedMessageError{agentIdentitiesAnswer, msgType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SignRequest requests the signing of data by the agent using a protocol 2 key
|
|
||||||
// as defined in [PROTOCOL.agent] section 2.6.2.
|
|
||||||
func (ac *AgentClient) SignRequest(key PublicKey, data []byte) ([]byte, error) {
|
|
||||||
req := marshal(agentSignRequest, signRequestAgentMsg{
|
|
||||||
KeyBlob: MarshalPublicKey(key),
|
|
||||||
Data: data,
|
|
||||||
})
|
|
||||||
|
|
||||||
msg, msgType, err := ac.sendAndReceive(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *signResponseAgentMsg:
|
|
||||||
return msg.SigBlob, nil
|
|
||||||
case *failureAgentMsg:
|
|
||||||
return nil, errors.New("ssh: failed to sign challenge")
|
|
||||||
}
|
|
||||||
return nil, UnexpectedMessageError{agentSignResponse, msgType}
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshalAgentMsg parses an agent message in packet, returning the parsed
|
|
||||||
// form and the message type of packet.
|
|
||||||
func unmarshalAgentMsg(packet []byte) (interface{}, uint8, error) {
|
|
||||||
if len(packet) < 1 {
|
|
||||||
return nil, 0, ParseError{0}
|
|
||||||
}
|
|
||||||
var msg interface{}
|
|
||||||
switch packet[0] {
|
|
||||||
case agentFailure:
|
|
||||||
msg = new(failureAgentMsg)
|
|
||||||
case agentSuccess:
|
|
||||||
msg = new(successAgentMsg)
|
|
||||||
case agentIdentitiesAnswer:
|
|
||||||
msg = new(identitiesAnswerAgentMsg)
|
|
||||||
case agentSignResponse:
|
|
||||||
msg = new(signResponseAgentMsg)
|
|
||||||
default:
|
|
||||||
return nil, 0, UnexpectedMessageError{0, packet[0]}
|
|
||||||
}
|
|
||||||
if err := unmarshal(msg, packet, packet[0]); err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
return msg, packet[0], nil
|
|
||||||
}
|
|
96
vendor/code.google.com/p/go.crypto/ssh/buffer.go
generated
vendored
96
vendor/code.google.com/p/go.crypto/ssh/buffer.go
generated
vendored
@ -1,96 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// buffer provides a linked list buffer for data exchange
|
|
||||||
// between producer and consumer. Theoretically the buffer is
|
|
||||||
// of unlimited capacity as it does no allocation of its own.
|
|
||||||
type buffer struct {
|
|
||||||
// protects concurrent access to head, tail and closed
|
|
||||||
*sync.Cond
|
|
||||||
|
|
||||||
head *element // the buffer that will be read first
|
|
||||||
tail *element // the buffer that will be read last
|
|
||||||
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// An element represents a single link in a linked list.
|
|
||||||
type element struct {
|
|
||||||
buf []byte
|
|
||||||
next *element
|
|
||||||
}
|
|
||||||
|
|
||||||
// newBuffer returns an empty buffer that is not closed.
|
|
||||||
func newBuffer() *buffer {
|
|
||||||
e := new(element)
|
|
||||||
b := &buffer{
|
|
||||||
Cond: newCond(),
|
|
||||||
head: e,
|
|
||||||
tail: e,
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// write makes buf available for Read to receive.
|
|
||||||
// buf must not be modified after the call to write.
|
|
||||||
func (b *buffer) write(buf []byte) {
|
|
||||||
b.Cond.L.Lock()
|
|
||||||
defer b.Cond.L.Unlock()
|
|
||||||
e := &element{buf: buf}
|
|
||||||
b.tail.next = e
|
|
||||||
b.tail = e
|
|
||||||
b.Cond.Signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
// eof closes the buffer. Reads from the buffer once all
|
|
||||||
// the data has been consumed will receive os.EOF.
|
|
||||||
func (b *buffer) eof() error {
|
|
||||||
b.Cond.L.Lock()
|
|
||||||
defer b.Cond.L.Unlock()
|
|
||||||
b.closed = true
|
|
||||||
b.Cond.Signal()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads data from the internal buffer in buf.
|
|
||||||
// Reads will block if no data is available, or until
|
|
||||||
// the buffer is closed.
|
|
||||||
func (b *buffer) Read(buf []byte) (n int, err error) {
|
|
||||||
b.Cond.L.Lock()
|
|
||||||
defer b.Cond.L.Unlock()
|
|
||||||
for len(buf) > 0 {
|
|
||||||
// if there is data in b.head, copy it
|
|
||||||
if len(b.head.buf) > 0 {
|
|
||||||
r := copy(buf, b.head.buf)
|
|
||||||
buf, b.head.buf = buf[r:], b.head.buf[r:]
|
|
||||||
n += r
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// if there is a next buffer, make it the head
|
|
||||||
if len(b.head.buf) == 0 && b.head != b.tail {
|
|
||||||
b.head = b.head.next
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// if at least one byte has been copied, return
|
|
||||||
if n > 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// if nothing was read, and there is nothing outstanding
|
|
||||||
// check to see if the buffer is closed.
|
|
||||||
if b.closed {
|
|
||||||
err = io.EOF
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// out of buffers, wait for producer
|
|
||||||
b.Cond.Wait()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
378
vendor/code.google.com/p/go.crypto/ssh/certs.go
generated
vendored
378
vendor/code.google.com/p/go.crypto/ssh/certs.go
generated
vendored
@ -1,378 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These constants from [PROTOCOL.certkeys] represent the algorithm names
|
|
||||||
// for certificate types supported by this package.
|
|
||||||
const (
|
|
||||||
CertAlgoRSAv01 = "ssh-rsa-cert-v01@openssh.com"
|
|
||||||
CertAlgoDSAv01 = "ssh-dss-cert-v01@openssh.com"
|
|
||||||
CertAlgoECDSA256v01 = "ecdsa-sha2-nistp256-cert-v01@openssh.com"
|
|
||||||
CertAlgoECDSA384v01 = "ecdsa-sha2-nistp384-cert-v01@openssh.com"
|
|
||||||
CertAlgoECDSA521v01 = "ecdsa-sha2-nistp521-cert-v01@openssh.com"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Certificate types are used to specify whether a certificate is for identification
|
|
||||||
// of a user or a host. Current identities are defined in [PROTOCOL.certkeys].
|
|
||||||
const (
|
|
||||||
UserCert = 1
|
|
||||||
HostCert = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type signature struct {
|
|
||||||
Format string
|
|
||||||
Blob []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type tuple struct {
|
|
||||||
Name string
|
|
||||||
Data string
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
maxUint64 = 1<<64 - 1
|
|
||||||
maxInt64 = 1<<63 - 1
|
|
||||||
)
|
|
||||||
|
|
||||||
// CertTime represents an unsigned 64-bit time value in seconds starting from
|
|
||||||
// UNIX epoch. We use CertTime instead of time.Time in order to properly handle
|
|
||||||
// the "infinite" time value ^0, which would become negative when expressed as
|
|
||||||
// an int64.
|
|
||||||
type CertTime uint64
|
|
||||||
|
|
||||||
func (ct CertTime) Time() time.Time {
|
|
||||||
if ct > maxInt64 {
|
|
||||||
return time.Unix(maxInt64, 0)
|
|
||||||
}
|
|
||||||
return time.Unix(int64(ct), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ct CertTime) IsInfinite() bool {
|
|
||||||
return ct == maxUint64
|
|
||||||
}
|
|
||||||
|
|
||||||
// An OpenSSHCertV01 represents an OpenSSH certificate as defined in
|
|
||||||
// [PROTOCOL.certkeys]?rev=1.8.
|
|
||||||
type OpenSSHCertV01 struct {
|
|
||||||
Nonce []byte
|
|
||||||
Key PublicKey
|
|
||||||
Serial uint64
|
|
||||||
Type uint32
|
|
||||||
KeyId string
|
|
||||||
ValidPrincipals []string
|
|
||||||
ValidAfter, ValidBefore CertTime
|
|
||||||
CriticalOptions []tuple
|
|
||||||
Extensions []tuple
|
|
||||||
Reserved []byte
|
|
||||||
SignatureKey PublicKey
|
|
||||||
Signature *signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateOpenSSHCertV01Signature uses the cert's SignatureKey to verify that
|
|
||||||
// the cert's Signature.Blob is the result of signing the cert bytes starting
|
|
||||||
// from the algorithm string and going up to and including the SignatureKey.
|
|
||||||
func validateOpenSSHCertV01Signature(cert *OpenSSHCertV01) bool {
|
|
||||||
return cert.SignatureKey.Verify(cert.BytesForSigning(), cert.Signature.Blob)
|
|
||||||
}
|
|
||||||
|
|
||||||
var certAlgoNames = map[string]string{
|
|
||||||
KeyAlgoRSA: CertAlgoRSAv01,
|
|
||||||
KeyAlgoDSA: CertAlgoDSAv01,
|
|
||||||
KeyAlgoECDSA256: CertAlgoECDSA256v01,
|
|
||||||
KeyAlgoECDSA384: CertAlgoECDSA384v01,
|
|
||||||
KeyAlgoECDSA521: CertAlgoECDSA521v01,
|
|
||||||
}
|
|
||||||
|
|
||||||
// certToPrivAlgo returns the underlying algorithm for a certificate algorithm.
|
|
||||||
// Panics if a non-certificate algorithm is passed.
|
|
||||||
func certToPrivAlgo(algo string) string {
|
|
||||||
for privAlgo, pubAlgo := range certAlgoNames {
|
|
||||||
if pubAlgo == algo {
|
|
||||||
return privAlgo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("unknown cert algorithm")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cert *OpenSSHCertV01) marshal(includeAlgo, includeSig bool) []byte {
|
|
||||||
algoName := cert.PublicKeyAlgo()
|
|
||||||
pubKey := cert.Key.Marshal()
|
|
||||||
sigKey := MarshalPublicKey(cert.SignatureKey)
|
|
||||||
|
|
||||||
var length int
|
|
||||||
if includeAlgo {
|
|
||||||
length += stringLength(len(algoName))
|
|
||||||
}
|
|
||||||
length += stringLength(len(cert.Nonce))
|
|
||||||
length += len(pubKey)
|
|
||||||
length += 8 // Length of Serial
|
|
||||||
length += 4 // Length of Type
|
|
||||||
length += stringLength(len(cert.KeyId))
|
|
||||||
length += lengthPrefixedNameListLength(cert.ValidPrincipals)
|
|
||||||
length += 8 // Length of ValidAfter
|
|
||||||
length += 8 // Length of ValidBefore
|
|
||||||
length += tupleListLength(cert.CriticalOptions)
|
|
||||||
length += tupleListLength(cert.Extensions)
|
|
||||||
length += stringLength(len(cert.Reserved))
|
|
||||||
length += stringLength(len(sigKey))
|
|
||||||
if includeSig {
|
|
||||||
length += signatureLength(cert.Signature)
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
r := ret
|
|
||||||
if includeAlgo {
|
|
||||||
r = marshalString(r, []byte(algoName))
|
|
||||||
}
|
|
||||||
r = marshalString(r, cert.Nonce)
|
|
||||||
copy(r, pubKey)
|
|
||||||
r = r[len(pubKey):]
|
|
||||||
r = marshalUint64(r, cert.Serial)
|
|
||||||
r = marshalUint32(r, cert.Type)
|
|
||||||
r = marshalString(r, []byte(cert.KeyId))
|
|
||||||
r = marshalLengthPrefixedNameList(r, cert.ValidPrincipals)
|
|
||||||
r = marshalUint64(r, uint64(cert.ValidAfter))
|
|
||||||
r = marshalUint64(r, uint64(cert.ValidBefore))
|
|
||||||
r = marshalTupleList(r, cert.CriticalOptions)
|
|
||||||
r = marshalTupleList(r, cert.Extensions)
|
|
||||||
r = marshalString(r, cert.Reserved)
|
|
||||||
r = marshalString(r, sigKey)
|
|
||||||
if includeSig {
|
|
||||||
r = marshalSignature(r, cert.Signature)
|
|
||||||
}
|
|
||||||
if len(r) > 0 {
|
|
||||||
panic("ssh: internal error, marshaling certificate did not fill the entire buffer")
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cert *OpenSSHCertV01) BytesForSigning() []byte {
|
|
||||||
return cert.marshal(true, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cert *OpenSSHCertV01) Marshal() []byte {
|
|
||||||
return cert.marshal(false, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *OpenSSHCertV01) PublicKeyAlgo() string {
|
|
||||||
algo, ok := certAlgoNames[c.Key.PublicKeyAlgo()]
|
|
||||||
if !ok {
|
|
||||||
panic("unknown cert key type")
|
|
||||||
}
|
|
||||||
return algo
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *OpenSSHCertV01) PrivateKeyAlgo() string {
|
|
||||||
return c.Key.PrivateKeyAlgo()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *OpenSSHCertV01) Verify(data []byte, sig []byte) bool {
|
|
||||||
return c.Key.Verify(data, sig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseOpenSSHCertV01(in []byte, algo string) (out *OpenSSHCertV01, rest []byte, ok bool) {
|
|
||||||
cert := new(OpenSSHCertV01)
|
|
||||||
|
|
||||||
if cert.Nonce, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
privAlgo := certToPrivAlgo(algo)
|
|
||||||
cert.Key, in, ok = parsePubKey(in, privAlgo)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// We test PublicKeyAlgo to make sure we don't use some weird sub-cert.
|
|
||||||
if cert.Key.PublicKeyAlgo() != privAlgo {
|
|
||||||
ok = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert.Serial, in, ok = parseUint64(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert.Type, in, ok = parseUint32(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
keyId, in, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cert.KeyId = string(keyId)
|
|
||||||
|
|
||||||
if cert.ValidPrincipals, in, ok = parseLengthPrefixedNameList(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
va, in, ok := parseUint64(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cert.ValidAfter = CertTime(va)
|
|
||||||
|
|
||||||
vb, in, ok := parseUint64(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cert.ValidBefore = CertTime(vb)
|
|
||||||
|
|
||||||
if cert.CriticalOptions, in, ok = parseTupleList(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert.Extensions, in, ok = parseTupleList(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert.Reserved, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
sigKey, in, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if cert.SignatureKey, _, ok = ParsePublicKey(sigKey); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert.Signature, in, ok = parseSignature(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
return cert, in, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func lengthPrefixedNameListLength(namelist []string) int {
|
|
||||||
length := 4 // length prefix for list
|
|
||||||
for _, name := range namelist {
|
|
||||||
length += 4 // length prefix for name
|
|
||||||
length += len(name)
|
|
||||||
}
|
|
||||||
return length
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalLengthPrefixedNameList(to []byte, namelist []string) []byte {
|
|
||||||
length := uint32(lengthPrefixedNameListLength(namelist) - 4)
|
|
||||||
to = marshalUint32(to, length)
|
|
||||||
for _, name := range namelist {
|
|
||||||
to = marshalString(to, []byte(name))
|
|
||||||
}
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseLengthPrefixedNameList(in []byte) (out []string, rest []byte, ok bool) {
|
|
||||||
list, rest, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(list) > 0 {
|
|
||||||
var next []byte
|
|
||||||
if next, list, ok = parseString(list); !ok {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
out = append(out, string(next))
|
|
||||||
}
|
|
||||||
ok = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func tupleListLength(tupleList []tuple) int {
|
|
||||||
length := 4 // length prefix for list
|
|
||||||
for _, t := range tupleList {
|
|
||||||
length += 4 // length prefix for t.Name
|
|
||||||
length += len(t.Name)
|
|
||||||
length += 4 // length prefix for t.Data
|
|
||||||
length += len(t.Data)
|
|
||||||
}
|
|
||||||
return length
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalTupleList(to []byte, tuplelist []tuple) []byte {
|
|
||||||
length := uint32(tupleListLength(tuplelist) - 4)
|
|
||||||
to = marshalUint32(to, length)
|
|
||||||
for _, t := range tuplelist {
|
|
||||||
to = marshalString(to, []byte(t.Name))
|
|
||||||
to = marshalString(to, []byte(t.Data))
|
|
||||||
}
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseTupleList(in []byte) (out []tuple, rest []byte, ok bool) {
|
|
||||||
list, rest, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for len(list) > 0 {
|
|
||||||
var name, data []byte
|
|
||||||
var ok bool
|
|
||||||
name, list, ok = parseString(list)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
data, list, ok = parseString(list)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
out = append(out, tuple{string(name), string(data)})
|
|
||||||
}
|
|
||||||
ok = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func signatureLength(sig *signature) int {
|
|
||||||
length := 4 // length prefix for signature
|
|
||||||
length += stringLength(len(sig.Format))
|
|
||||||
length += stringLength(len(sig.Blob))
|
|
||||||
return length
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalSignature(to []byte, sig *signature) []byte {
|
|
||||||
length := uint32(signatureLength(sig) - 4)
|
|
||||||
to = marshalUint32(to, length)
|
|
||||||
to = marshalString(to, []byte(sig.Format))
|
|
||||||
to = marshalString(to, sig.Blob)
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSignatureBody(in []byte) (out *signature, rest []byte, ok bool) {
|
|
||||||
var format []byte
|
|
||||||
if format, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out = &signature{
|
|
||||||
Format: string(format),
|
|
||||||
}
|
|
||||||
|
|
||||||
if out.Blob, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return out, in, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSignature(in []byte) (out *signature, rest []byte, ok bool) {
|
|
||||||
var sigBytes []byte
|
|
||||||
if sigBytes, rest, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
out, sigBytes, ok = parseSignatureBody(sigBytes)
|
|
||||||
if !ok || len(sigBytes) > 0 {
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
594
vendor/code.google.com/p/go.crypto/ssh/channel.go
generated
vendored
594
vendor/code.google.com/p/go.crypto/ssh/channel.go
generated
vendored
@ -1,594 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// extendedDataTypeCode identifies an OpenSSL extended data type. See RFC 4254,
|
|
||||||
// section 5.2.
|
|
||||||
type extendedDataTypeCode uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
// extendedDataStderr is the extended data type that is used for stderr.
|
|
||||||
extendedDataStderr extendedDataTypeCode = 1
|
|
||||||
|
|
||||||
// minPacketLength defines the smallest valid packet
|
|
||||||
minPacketLength = 9
|
|
||||||
|
|
||||||
// channelMaxPacketSize defines the maximum packet size advertised in open messages
|
|
||||||
channelMaxPacketSize = 1 << 15 // RFC 4253 6.1, minimum 32 KiB
|
|
||||||
|
|
||||||
// channelWindowSize defines the window size advertised in open messages
|
|
||||||
channelWindowSize = 64 * channelMaxPacketSize // Like OpenSSH
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Channel is an ordered, reliable, duplex stream that is multiplexed over an
|
|
||||||
// SSH connection. Channel.Read can return a ChannelRequest as an error.
|
|
||||||
type Channel interface {
|
|
||||||
// Accept accepts the channel creation request.
|
|
||||||
Accept() error
|
|
||||||
// Reject rejects the channel creation request. After calling this, no
|
|
||||||
// other methods on the Channel may be called. If they are then the
|
|
||||||
// peer is likely to signal a protocol error and drop the connection.
|
|
||||||
Reject(reason RejectionReason, message string) error
|
|
||||||
|
|
||||||
// Read may return a ChannelRequest as an error.
|
|
||||||
Read(data []byte) (int, error)
|
|
||||||
Write(data []byte) (int, error)
|
|
||||||
Close() error
|
|
||||||
|
|
||||||
// Stderr returns an io.Writer that writes to this channel with the
|
|
||||||
// extended data type set to stderr.
|
|
||||||
Stderr() io.Writer
|
|
||||||
|
|
||||||
// AckRequest either sends an ack or nack to the channel request.
|
|
||||||
AckRequest(ok bool) error
|
|
||||||
|
|
||||||
// ChannelType returns the type of the channel, as supplied by the
|
|
||||||
// client.
|
|
||||||
ChannelType() string
|
|
||||||
// ExtraData returns the arbitrary payload for this channel, as supplied
|
|
||||||
// by the client. This data is specific to the channel type.
|
|
||||||
ExtraData() []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChannelRequest represents a request sent on a channel, outside of the normal
|
|
||||||
// stream of bytes. It may result from calling Read on a Channel.
|
|
||||||
type ChannelRequest struct {
|
|
||||||
Request string
|
|
||||||
WantReply bool
|
|
||||||
Payload []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c ChannelRequest) Error() string {
|
|
||||||
return "ssh: channel request received"
|
|
||||||
}
|
|
||||||
|
|
||||||
// RejectionReason is an enumeration used when rejecting channel creation
|
|
||||||
// requests. See RFC 4254, section 5.1.
|
|
||||||
type RejectionReason uint32
|
|
||||||
|
|
||||||
const (
|
|
||||||
Prohibited RejectionReason = iota + 1
|
|
||||||
ConnectionFailed
|
|
||||||
UnknownChannelType
|
|
||||||
ResourceShortage
|
|
||||||
)
|
|
||||||
|
|
||||||
// String converts the rejection reason to human readable form.
|
|
||||||
func (r RejectionReason) String() string {
|
|
||||||
switch r {
|
|
||||||
case Prohibited:
|
|
||||||
return "administratively prohibited"
|
|
||||||
case ConnectionFailed:
|
|
||||||
return "connect failed"
|
|
||||||
case UnknownChannelType:
|
|
||||||
return "unknown channel type"
|
|
||||||
case ResourceShortage:
|
|
||||||
return "resource shortage"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("unknown reason %d", int(r))
|
|
||||||
}
|
|
||||||
|
|
||||||
type channel struct {
|
|
||||||
packetConn // the underlying transport
|
|
||||||
localId, remoteId uint32
|
|
||||||
remoteWin window
|
|
||||||
maxPacket uint32
|
|
||||||
isClosed uint32 // atomic bool, non zero if true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) sendWindowAdj(n int) error {
|
|
||||||
msg := windowAdjustMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
AdditionalBytes: uint32(n),
|
|
||||||
}
|
|
||||||
return c.writePacket(marshal(msgChannelWindowAdjust, msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendEOF sends EOF to the remote side. RFC 4254 Section 5.3
|
|
||||||
func (c *channel) sendEOF() error {
|
|
||||||
return c.writePacket(marshal(msgChannelEOF, channelEOFMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendClose informs the remote side of our intent to close the channel.
|
|
||||||
func (c *channel) sendClose() error {
|
|
||||||
return c.packetConn.writePacket(marshal(msgChannelClose, channelCloseMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) sendChannelOpenFailure(reason RejectionReason, message string) error {
|
|
||||||
reject := channelOpenFailureMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
Reason: reason,
|
|
||||||
Message: message,
|
|
||||||
Language: "en",
|
|
||||||
}
|
|
||||||
return c.writePacket(marshal(msgChannelOpenFailure, reject))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) writePacket(b []byte) error {
|
|
||||||
if c.closed() {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
if uint32(len(b)) > c.maxPacket {
|
|
||||||
return fmt.Errorf("ssh: cannot write %d bytes, maxPacket is %d bytes", len(b), c.maxPacket)
|
|
||||||
}
|
|
||||||
return c.packetConn.writePacket(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) closed() bool {
|
|
||||||
return atomic.LoadUint32(&c.isClosed) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *channel) setClosed() bool {
|
|
||||||
return atomic.CompareAndSwapUint32(&c.isClosed, 0, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
type serverChan struct {
|
|
||||||
channel
|
|
||||||
// immutable once created
|
|
||||||
chanType string
|
|
||||||
extraData []byte
|
|
||||||
|
|
||||||
serverConn *ServerConn
|
|
||||||
myWindow uint32
|
|
||||||
theyClosed bool // indicates the close msg has been received from the remote side
|
|
||||||
theySentEOF bool
|
|
||||||
isDead uint32
|
|
||||||
err error
|
|
||||||
|
|
||||||
pendingRequests []ChannelRequest
|
|
||||||
pendingData []byte
|
|
||||||
head, length int
|
|
||||||
|
|
||||||
// This lock is inferior to serverConn.lock
|
|
||||||
cond *sync.Cond
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) Accept() error {
|
|
||||||
c.serverConn.lock.Lock()
|
|
||||||
defer c.serverConn.lock.Unlock()
|
|
||||||
|
|
||||||
if c.serverConn.err != nil {
|
|
||||||
return c.serverConn.err
|
|
||||||
}
|
|
||||||
|
|
||||||
confirm := channelOpenConfirmMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
MyId: c.localId,
|
|
||||||
MyWindow: c.myWindow,
|
|
||||||
MaxPacketSize: c.maxPacket,
|
|
||||||
}
|
|
||||||
return c.writePacket(marshal(msgChannelOpenConfirm, confirm))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) Reject(reason RejectionReason, message string) error {
|
|
||||||
c.serverConn.lock.Lock()
|
|
||||||
defer c.serverConn.lock.Unlock()
|
|
||||||
|
|
||||||
if c.serverConn.err != nil {
|
|
||||||
return c.serverConn.err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.sendChannelOpenFailure(reason, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) handlePacket(packet interface{}) {
|
|
||||||
c.cond.L.Lock()
|
|
||||||
defer c.cond.L.Unlock()
|
|
||||||
|
|
||||||
switch packet := packet.(type) {
|
|
||||||
case *channelRequestMsg:
|
|
||||||
req := ChannelRequest{
|
|
||||||
Request: packet.Request,
|
|
||||||
WantReply: packet.WantReply,
|
|
||||||
Payload: packet.RequestSpecificData,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.pendingRequests = append(c.pendingRequests, req)
|
|
||||||
c.cond.Signal()
|
|
||||||
case *channelCloseMsg:
|
|
||||||
c.theyClosed = true
|
|
||||||
c.cond.Signal()
|
|
||||||
case *channelEOFMsg:
|
|
||||||
c.theySentEOF = true
|
|
||||||
c.cond.Signal()
|
|
||||||
case *windowAdjustMsg:
|
|
||||||
if !c.remoteWin.add(packet.AdditionalBytes) {
|
|
||||||
panic("illegal window update")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unknown packet type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) handleData(data []byte) {
|
|
||||||
c.cond.L.Lock()
|
|
||||||
defer c.cond.L.Unlock()
|
|
||||||
|
|
||||||
// The other side should never send us more than our window.
|
|
||||||
if len(data)+c.length > len(c.pendingData) {
|
|
||||||
// TODO(agl): we should tear down the channel with a protocol
|
|
||||||
// error.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.myWindow -= uint32(len(data))
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
tail := c.head + c.length
|
|
||||||
if tail >= len(c.pendingData) {
|
|
||||||
tail -= len(c.pendingData)
|
|
||||||
}
|
|
||||||
n := copy(c.pendingData[tail:], data)
|
|
||||||
data = data[n:]
|
|
||||||
c.length += n
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cond.Signal()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) Stderr() io.Writer {
|
|
||||||
return extendedDataChannel{c: c, t: extendedDataStderr}
|
|
||||||
}
|
|
||||||
|
|
||||||
// extendedDataChannel is an io.Writer that writes any data to c as extended
|
|
||||||
// data of the given type.
|
|
||||||
type extendedDataChannel struct {
|
|
||||||
t extendedDataTypeCode
|
|
||||||
c *serverChan
|
|
||||||
}
|
|
||||||
|
|
||||||
func (edc extendedDataChannel) Write(data []byte) (n int, err error) {
|
|
||||||
const headerLength = 13 // 1 byte message type, 4 bytes remoteId, 4 bytes extended message type, 4 bytes data length
|
|
||||||
c := edc.c
|
|
||||||
for len(data) > 0 {
|
|
||||||
space := min(c.maxPacket-headerLength, len(data))
|
|
||||||
if space, err = c.getWindowSpace(space); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
todo := data
|
|
||||||
if uint32(len(todo)) > space {
|
|
||||||
todo = todo[:space]
|
|
||||||
}
|
|
||||||
|
|
||||||
packet := make([]byte, headerLength+len(todo))
|
|
||||||
packet[0] = msgChannelExtendedData
|
|
||||||
marshalUint32(packet[1:], c.remoteId)
|
|
||||||
marshalUint32(packet[5:], uint32(edc.t))
|
|
||||||
marshalUint32(packet[9:], uint32(len(todo)))
|
|
||||||
copy(packet[13:], todo)
|
|
||||||
|
|
||||||
if err = c.writePacket(packet); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n += len(todo)
|
|
||||||
data = data[len(todo):]
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) Read(data []byte) (n int, err error) {
|
|
||||||
n, err, windowAdjustment := c.read(data)
|
|
||||||
|
|
||||||
if windowAdjustment > 0 {
|
|
||||||
packet := marshal(msgChannelWindowAdjust, windowAdjustMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
AdditionalBytes: windowAdjustment,
|
|
||||||
})
|
|
||||||
err = c.writePacket(packet)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) read(data []byte) (n int, err error, windowAdjustment uint32) {
|
|
||||||
c.cond.L.Lock()
|
|
||||||
defer c.cond.L.Unlock()
|
|
||||||
|
|
||||||
if c.err != nil {
|
|
||||||
return 0, c.err, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
if c.theySentEOF || c.theyClosed || c.dead() {
|
|
||||||
return 0, io.EOF, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.pendingRequests) > 0 {
|
|
||||||
req := c.pendingRequests[0]
|
|
||||||
if len(c.pendingRequests) == 1 {
|
|
||||||
c.pendingRequests = nil
|
|
||||||
} else {
|
|
||||||
oldPendingRequests := c.pendingRequests
|
|
||||||
c.pendingRequests = make([]ChannelRequest, len(oldPendingRequests)-1)
|
|
||||||
copy(c.pendingRequests, oldPendingRequests[1:])
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0, req, 0
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.length > 0 {
|
|
||||||
tail := min(uint32(c.head+c.length), len(c.pendingData))
|
|
||||||
n = copy(data, c.pendingData[c.head:tail])
|
|
||||||
c.head += n
|
|
||||||
c.length -= n
|
|
||||||
if c.head == len(c.pendingData) {
|
|
||||||
c.head = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
windowAdjustment = uint32(len(c.pendingData)-c.length) - c.myWindow
|
|
||||||
if windowAdjustment < uint32(len(c.pendingData)/2) {
|
|
||||||
windowAdjustment = 0
|
|
||||||
}
|
|
||||||
c.myWindow += windowAdjustment
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cond.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// getWindowSpace takes, at most, max bytes of space from the peer's window. It
|
|
||||||
// returns the number of bytes actually reserved.
|
|
||||||
func (c *serverChan) getWindowSpace(max uint32) (uint32, error) {
|
|
||||||
if c.dead() || c.closed() {
|
|
||||||
return 0, io.EOF
|
|
||||||
}
|
|
||||||
return c.remoteWin.reserve(max), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) dead() bool {
|
|
||||||
return atomic.LoadUint32(&c.isDead) > 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) setDead() {
|
|
||||||
atomic.StoreUint32(&c.isDead, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) Write(data []byte) (n int, err error) {
|
|
||||||
const headerLength = 9 // 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
|
||||||
for len(data) > 0 {
|
|
||||||
space := min(c.maxPacket-headerLength, len(data))
|
|
||||||
if space, err = c.getWindowSpace(space); err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
todo := data
|
|
||||||
if uint32(len(todo)) > space {
|
|
||||||
todo = todo[:space]
|
|
||||||
}
|
|
||||||
|
|
||||||
packet := make([]byte, headerLength+len(todo))
|
|
||||||
packet[0] = msgChannelData
|
|
||||||
marshalUint32(packet[1:], c.remoteId)
|
|
||||||
marshalUint32(packet[5:], uint32(len(todo)))
|
|
||||||
copy(packet[9:], todo)
|
|
||||||
|
|
||||||
if err = c.writePacket(packet); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
n += len(todo)
|
|
||||||
data = data[len(todo):]
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close signals the intent to close the channel.
|
|
||||||
func (c *serverChan) Close() error {
|
|
||||||
c.serverConn.lock.Lock()
|
|
||||||
defer c.serverConn.lock.Unlock()
|
|
||||||
|
|
||||||
if c.serverConn.err != nil {
|
|
||||||
return c.serverConn.err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !c.setClosed() {
|
|
||||||
return errors.New("ssh: channel already closed")
|
|
||||||
}
|
|
||||||
return c.sendClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) AckRequest(ok bool) error {
|
|
||||||
c.serverConn.lock.Lock()
|
|
||||||
defer c.serverConn.lock.Unlock()
|
|
||||||
|
|
||||||
if c.serverConn.err != nil {
|
|
||||||
return c.serverConn.err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
ack := channelRequestFailureMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
}
|
|
||||||
return c.writePacket(marshal(msgChannelFailure, ack))
|
|
||||||
}
|
|
||||||
|
|
||||||
ack := channelRequestSuccessMsg{
|
|
||||||
PeersId: c.remoteId,
|
|
||||||
}
|
|
||||||
return c.writePacket(marshal(msgChannelSuccess, ack))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) ChannelType() string {
|
|
||||||
return c.chanType
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *serverChan) ExtraData() []byte {
|
|
||||||
return c.extraData
|
|
||||||
}
|
|
||||||
|
|
||||||
// A clientChan represents a single RFC 4254 channel multiplexed
|
|
||||||
// over a SSH connection.
|
|
||||||
type clientChan struct {
|
|
||||||
channel
|
|
||||||
stdin *chanWriter
|
|
||||||
stdout *chanReader
|
|
||||||
stderr *chanReader
|
|
||||||
msg chan interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// newClientChan returns a partially constructed *clientChan
|
|
||||||
// using the local id provided. To be usable clientChan.remoteId
|
|
||||||
// needs to be assigned once known.
|
|
||||||
func newClientChan(cc packetConn, id uint32) *clientChan {
|
|
||||||
c := &clientChan{
|
|
||||||
channel: channel{
|
|
||||||
packetConn: cc,
|
|
||||||
localId: id,
|
|
||||||
remoteWin: window{Cond: newCond()},
|
|
||||||
},
|
|
||||||
msg: make(chan interface{}, 16),
|
|
||||||
}
|
|
||||||
c.stdin = &chanWriter{
|
|
||||||
channel: &c.channel,
|
|
||||||
}
|
|
||||||
c.stdout = &chanReader{
|
|
||||||
channel: &c.channel,
|
|
||||||
buffer: newBuffer(),
|
|
||||||
}
|
|
||||||
c.stderr = &chanReader{
|
|
||||||
channel: &c.channel,
|
|
||||||
buffer: newBuffer(),
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
// waitForChannelOpenResponse, if successful, fills out
|
|
||||||
// the remoteId and records any initial window advertisement.
|
|
||||||
func (c *clientChan) waitForChannelOpenResponse() error {
|
|
||||||
switch msg := (<-c.msg).(type) {
|
|
||||||
case *channelOpenConfirmMsg:
|
|
||||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
|
||||||
return errors.New("ssh: invalid MaxPacketSize from peer")
|
|
||||||
}
|
|
||||||
// fixup remoteId field
|
|
||||||
c.remoteId = msg.MyId
|
|
||||||
c.maxPacket = msg.MaxPacketSize
|
|
||||||
c.remoteWin.add(msg.MyWindow)
|
|
||||||
return nil
|
|
||||||
case *channelOpenFailureMsg:
|
|
||||||
return errors.New(safeString(msg.Message))
|
|
||||||
}
|
|
||||||
return errors.New("ssh: unexpected packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close signals the intent to close the channel.
|
|
||||||
func (c *clientChan) Close() error {
|
|
||||||
if !c.setClosed() {
|
|
||||||
return errors.New("ssh: channel already closed")
|
|
||||||
}
|
|
||||||
c.stdout.eof()
|
|
||||||
c.stderr.eof()
|
|
||||||
return c.sendClose()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A chanWriter represents the stdin of a remote process.
|
|
||||||
type chanWriter struct {
|
|
||||||
*channel
|
|
||||||
// indicates the writer has been closed. eof is owned by the
|
|
||||||
// caller of Write/Close.
|
|
||||||
eof bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write writes data to the remote process's standard input.
|
|
||||||
func (w *chanWriter) Write(data []byte) (written int, err error) {
|
|
||||||
const headerLength = 9 // 1 byte message type, 4 bytes remoteId, 4 bytes data length
|
|
||||||
for len(data) > 0 {
|
|
||||||
if w.eof || w.closed() {
|
|
||||||
err = io.EOF
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// never send more data than maxPacket even if
|
|
||||||
// there is sufficient window.
|
|
||||||
n := min(w.maxPacket-headerLength, len(data))
|
|
||||||
r := w.remoteWin.reserve(n)
|
|
||||||
n = r
|
|
||||||
remoteId := w.remoteId
|
|
||||||
packet := []byte{
|
|
||||||
msgChannelData,
|
|
||||||
byte(remoteId >> 24), byte(remoteId >> 16), byte(remoteId >> 8), byte(remoteId),
|
|
||||||
byte(n >> 24), byte(n >> 16), byte(n >> 8), byte(n),
|
|
||||||
}
|
|
||||||
if err = w.writePacket(append(packet, data[:n]...)); err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
data = data[n:]
|
|
||||||
written += int(n)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func min(a uint32, b int) uint32 {
|
|
||||||
if a < uint32(b) {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return uint32(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *chanWriter) Close() error {
|
|
||||||
w.eof = true
|
|
||||||
return w.sendEOF()
|
|
||||||
}
|
|
||||||
|
|
||||||
// A chanReader represents stdout or stderr of a remote process.
|
|
||||||
type chanReader struct {
|
|
||||||
*channel // the channel backing this reader
|
|
||||||
*buffer
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read reads data from the remote process's stdout or stderr.
|
|
||||||
func (r *chanReader) Read(buf []byte) (int, error) {
|
|
||||||
n, err := r.buffer.Read(buf)
|
|
||||||
if err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
return n, err
|
|
||||||
}
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
err = r.sendWindowAdj(n)
|
|
||||||
if err == io.EOF && n > 0 {
|
|
||||||
// sendWindowAdjust can return io.EOF if the remote peer has
|
|
||||||
// closed the connection, however we want to defer forwarding io.EOF to the
|
|
||||||
// caller of Read until the buffer has been drained.
|
|
||||||
err = nil
|
|
||||||
}
|
|
||||||
return n, err
|
|
||||||
}
|
|
100
vendor/code.google.com/p/go.crypto/ssh/cipher.go
generated
vendored
100
vendor/code.google.com/p/go.crypto/ssh/cipher.go
generated
vendored
@ -1,100 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/aes"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/rc4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// streamDump is used to dump the initial keystream for stream ciphers. It is a
|
|
||||||
// a write-only buffer, and not intended for reading so do not require a mutex.
|
|
||||||
var streamDump [512]byte
|
|
||||||
|
|
||||||
// noneCipher implements cipher.Stream and provides no encryption. It is used
|
|
||||||
// by the transport before the first key-exchange.
|
|
||||||
type noneCipher struct{}
|
|
||||||
|
|
||||||
func (c noneCipher) XORKeyStream(dst, src []byte) {
|
|
||||||
copy(dst, src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAESCTR(key, iv []byte) (cipher.Stream, error) {
|
|
||||||
c, err := aes.NewCipher(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cipher.NewCTR(c, iv), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRC4(key, iv []byte) (cipher.Stream, error) {
|
|
||||||
return rc4.NewCipher(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
type cipherMode struct {
|
|
||||||
keySize int
|
|
||||||
ivSize int
|
|
||||||
skip int
|
|
||||||
createFunc func(key, iv []byte) (cipher.Stream, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *cipherMode) createCipher(key, iv []byte) (cipher.Stream, error) {
|
|
||||||
if len(key) < c.keySize {
|
|
||||||
panic("ssh: key length too small for cipher")
|
|
||||||
}
|
|
||||||
if len(iv) < c.ivSize {
|
|
||||||
panic("ssh: iv too small for cipher")
|
|
||||||
}
|
|
||||||
|
|
||||||
stream, err := c.createFunc(key[:c.keySize], iv[:c.ivSize])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for remainingToDump := c.skip; remainingToDump > 0; {
|
|
||||||
dumpThisTime := remainingToDump
|
|
||||||
if dumpThisTime > len(streamDump) {
|
|
||||||
dumpThisTime = len(streamDump)
|
|
||||||
}
|
|
||||||
stream.XORKeyStream(streamDump[:dumpThisTime], streamDump[:dumpThisTime])
|
|
||||||
remainingToDump -= dumpThisTime
|
|
||||||
}
|
|
||||||
|
|
||||||
return stream, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Specifies a default set of ciphers and a preference order. This is based on
|
|
||||||
// OpenSSH's default client preference order, minus algorithms that are not
|
|
||||||
// implemented.
|
|
||||||
var DefaultCipherOrder = []string{
|
|
||||||
"aes128-ctr", "aes192-ctr", "aes256-ctr",
|
|
||||||
"arcfour256", "arcfour128",
|
|
||||||
}
|
|
||||||
|
|
||||||
// cipherModes documents properties of supported ciphers. Ciphers not included
|
|
||||||
// are not supported and will not be negotiated, even if explicitly requested in
|
|
||||||
// ClientConfig.Crypto.Ciphers.
|
|
||||||
var cipherModes = map[string]*cipherMode{
|
|
||||||
// Ciphers from RFC4344, which introduced many CTR-based ciphers. Algorithms
|
|
||||||
// are defined in the order specified in the RFC.
|
|
||||||
"aes128-ctr": {16, aes.BlockSize, 0, newAESCTR},
|
|
||||||
"aes192-ctr": {24, aes.BlockSize, 0, newAESCTR},
|
|
||||||
"aes256-ctr": {32, aes.BlockSize, 0, newAESCTR},
|
|
||||||
|
|
||||||
// Ciphers from RFC4345, which introduces security-improved arcfour ciphers.
|
|
||||||
// They are defined in the order specified in the RFC.
|
|
||||||
"arcfour128": {16, 0, 1536, newRC4},
|
|
||||||
"arcfour256": {32, 0, 1536, newRC4},
|
|
||||||
}
|
|
||||||
|
|
||||||
// defaultKeyExchangeOrder specifies a default set of key exchange algorithms
|
|
||||||
// with preferences.
|
|
||||||
var defaultKeyExchangeOrder = []string{
|
|
||||||
// P384 and P521 are not constant-time yet, but since we don't
|
|
||||||
// reuse ephemeral keys, using them for ECDH should be OK.
|
|
||||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
|
||||||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
|
||||||
}
|
|
524
vendor/code.google.com/p/go.crypto/ssh/client.go
generated
vendored
524
vendor/code.google.com/p/go.crypto/ssh/client.go
generated
vendored
@ -1,524 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ClientConn represents the client side of an SSH connection.
|
|
||||||
type ClientConn struct {
|
|
||||||
transport *transport
|
|
||||||
config *ClientConfig
|
|
||||||
chanList // channels associated with this connection
|
|
||||||
forwardList // forwarded tcpip connections from the remote side
|
|
||||||
globalRequest
|
|
||||||
|
|
||||||
// Address as passed to the Dial function.
|
|
||||||
dialAddress string
|
|
||||||
|
|
||||||
serverVersion string
|
|
||||||
}
|
|
||||||
|
|
||||||
type globalRequest struct {
|
|
||||||
sync.Mutex
|
|
||||||
response chan interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client returns a new SSH client connection using c as the underlying transport.
|
|
||||||
func Client(c net.Conn, config *ClientConfig) (*ClientConn, error) {
|
|
||||||
return clientWithAddress(c, "", config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func clientWithAddress(c net.Conn, addr string, config *ClientConfig) (*ClientConn, error) {
|
|
||||||
conn := &ClientConn{
|
|
||||||
transport: newTransport(c, config.rand(), true /* is client */),
|
|
||||||
config: config,
|
|
||||||
globalRequest: globalRequest{response: make(chan interface{}, 1)},
|
|
||||||
dialAddress: addr,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := conn.handshake(); err != nil {
|
|
||||||
conn.transport.Close()
|
|
||||||
return nil, fmt.Errorf("handshake failed: %v", err)
|
|
||||||
}
|
|
||||||
go conn.mainLoop()
|
|
||||||
return conn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
func (c *ClientConn) Close() error { return c.transport.Close() }
|
|
||||||
|
|
||||||
// LocalAddr returns the local network address.
|
|
||||||
func (c *ClientConn) LocalAddr() net.Addr { return c.transport.LocalAddr() }
|
|
||||||
|
|
||||||
// RemoteAddr returns the remote network address.
|
|
||||||
func (c *ClientConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() }
|
|
||||||
|
|
||||||
// handshake performs the client side key exchange. See RFC 4253 Section 7.
|
|
||||||
func (c *ClientConn) handshake() error {
|
|
||||||
clientVersion := []byte(packageVersion)
|
|
||||||
if c.config.ClientVersion != "" {
|
|
||||||
clientVersion = []byte(c.config.ClientVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
serverVersion, err := exchangeVersions(c.transport.Conn, clientVersion)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.serverVersion = string(serverVersion)
|
|
||||||
|
|
||||||
clientKexInit := kexInitMsg{
|
|
||||||
KexAlgos: c.config.Crypto.kexes(),
|
|
||||||
ServerHostKeyAlgos: supportedHostKeyAlgos,
|
|
||||||
CiphersClientServer: c.config.Crypto.ciphers(),
|
|
||||||
CiphersServerClient: c.config.Crypto.ciphers(),
|
|
||||||
MACsClientServer: c.config.Crypto.macs(),
|
|
||||||
MACsServerClient: c.config.Crypto.macs(),
|
|
||||||
CompressionClientServer: supportedCompressions,
|
|
||||||
CompressionServerClient: supportedCompressions,
|
|
||||||
}
|
|
||||||
kexInitPacket := marshal(msgKexInit, clientKexInit)
|
|
||||||
if err := c.transport.writePacket(kexInitPacket); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
packet, err := c.transport.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var serverKexInit kexInitMsg
|
|
||||||
if err = unmarshal(&serverKexInit, packet, msgKexInit); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
algs := findAgreedAlgorithms(&clientKexInit, &serverKexInit)
|
|
||||||
if algs == nil {
|
|
||||||
return errors.New("ssh: no common algorithms")
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverKexInit.FirstKexFollows && algs.kex != serverKexInit.KexAlgos[0] {
|
|
||||||
// The server sent a Kex message for the wrong algorithm,
|
|
||||||
// which we have to ignore.
|
|
||||||
if _, err := c.transport.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kex, ok := kexAlgoMap[algs.kex]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
|
|
||||||
}
|
|
||||||
|
|
||||||
magics := handshakeMagics{
|
|
||||||
clientVersion: clientVersion,
|
|
||||||
serverVersion: serverVersion,
|
|
||||||
clientKexInit: kexInitPacket,
|
|
||||||
serverKexInit: packet,
|
|
||||||
}
|
|
||||||
result, err := kex.Client(c.transport, c.config.rand(), &magics)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = verifyHostKeySignature(algs.hostKey, result.HostKey, result.H, result.Signature)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if checker := c.config.HostKeyChecker; checker != nil {
|
|
||||||
err = checker.Check(c.dialAddress, c.transport.RemoteAddr(), algs.hostKey, result.HostKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.transport.prepareKeyChange(algs, result)
|
|
||||||
|
|
||||||
if err = c.transport.writePacket([]byte{msgNewKeys}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if packet, err = c.transport.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if packet[0] != msgNewKeys {
|
|
||||||
return UnexpectedMessageError{msgNewKeys, packet[0]}
|
|
||||||
}
|
|
||||||
return c.authenticate()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify the host key obtained in the key exchange.
|
|
||||||
func verifyHostKeySignature(hostKeyAlgo string, hostKeyBytes []byte, data []byte, signature []byte) error {
|
|
||||||
hostKey, rest, ok := ParsePublicKey(hostKeyBytes)
|
|
||||||
if len(rest) > 0 || !ok {
|
|
||||||
return errors.New("ssh: could not parse hostkey")
|
|
||||||
}
|
|
||||||
|
|
||||||
sig, rest, ok := parseSignatureBody(signature)
|
|
||||||
if len(rest) > 0 || !ok {
|
|
||||||
return errors.New("ssh: signature parse error")
|
|
||||||
}
|
|
||||||
if sig.Format != hostKeyAlgo {
|
|
||||||
return fmt.Errorf("ssh: unexpected signature type %q", sig.Format)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !hostKey.Verify(data, sig.Blob) {
|
|
||||||
return errors.New("ssh: host key signature error")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mainLoop reads incoming messages and routes channel messages
|
|
||||||
// to their respective ClientChans.
|
|
||||||
func (c *ClientConn) mainLoop() {
|
|
||||||
defer func() {
|
|
||||||
c.transport.Close()
|
|
||||||
c.chanList.closeAll()
|
|
||||||
c.forwardList.closeAll()
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
packet, err := c.transport.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
// TODO(dfc) A note on blocking channel use.
|
|
||||||
// The msg, data and dataExt channels of a clientChan can
|
|
||||||
// cause this loop to block indefinitely if the consumer does
|
|
||||||
// not service them.
|
|
||||||
switch packet[0] {
|
|
||||||
case msgChannelData:
|
|
||||||
if len(packet) < 9 {
|
|
||||||
// malformed data packet
|
|
||||||
return
|
|
||||||
}
|
|
||||||
remoteId := binary.BigEndian.Uint32(packet[1:5])
|
|
||||||
length := binary.BigEndian.Uint32(packet[5:9])
|
|
||||||
packet = packet[9:]
|
|
||||||
|
|
||||||
if length != uint32(len(packet)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch, ok := c.getChan(remoteId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.stdout.write(packet)
|
|
||||||
case msgChannelExtendedData:
|
|
||||||
if len(packet) < 13 {
|
|
||||||
// malformed data packet
|
|
||||||
return
|
|
||||||
}
|
|
||||||
remoteId := binary.BigEndian.Uint32(packet[1:5])
|
|
||||||
datatype := binary.BigEndian.Uint32(packet[5:9])
|
|
||||||
length := binary.BigEndian.Uint32(packet[9:13])
|
|
||||||
packet = packet[13:]
|
|
||||||
|
|
||||||
if length != uint32(len(packet)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// RFC 4254 5.2 defines data_type_code 1 to be data destined
|
|
||||||
// for stderr on interactive sessions. Other data types are
|
|
||||||
// silently discarded.
|
|
||||||
if datatype == 1 {
|
|
||||||
ch, ok := c.getChan(remoteId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.stderr.write(packet)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
decoded, err := decode(packet)
|
|
||||||
if err != nil {
|
|
||||||
if _, ok := err.(UnexpectedMessageError); ok {
|
|
||||||
fmt.Printf("mainLoop: unexpected message: %v\n", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch msg := decoded.(type) {
|
|
||||||
case *channelOpenMsg:
|
|
||||||
c.handleChanOpen(msg)
|
|
||||||
case *channelOpenConfirmMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.msg <- msg
|
|
||||||
case *channelOpenFailureMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.msg <- msg
|
|
||||||
case *channelCloseMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.Close()
|
|
||||||
close(ch.msg)
|
|
||||||
c.chanList.remove(msg.PeersId)
|
|
||||||
case *channelEOFMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.stdout.eof()
|
|
||||||
// RFC 4254 is mute on how EOF affects dataExt messages but
|
|
||||||
// it is logical to signal EOF at the same time.
|
|
||||||
ch.stderr.eof()
|
|
||||||
case *channelRequestSuccessMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.msg <- msg
|
|
||||||
case *channelRequestFailureMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.msg <- msg
|
|
||||||
case *channelRequestMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch.msg <- msg
|
|
||||||
case *windowAdjustMsg:
|
|
||||||
ch, ok := c.getChan(msg.PeersId)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !ch.remoteWin.add(msg.AdditionalBytes) {
|
|
||||||
// invalid window update
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case *globalRequestMsg:
|
|
||||||
// This handles keepalive messages and matches
|
|
||||||
// the behaviour of OpenSSH.
|
|
||||||
if msg.WantReply {
|
|
||||||
c.transport.writePacket(marshal(msgRequestFailure, globalRequestFailureMsg{}))
|
|
||||||
}
|
|
||||||
case *globalRequestSuccessMsg, *globalRequestFailureMsg:
|
|
||||||
c.globalRequest.response <- msg
|
|
||||||
case *disconnectMsg:
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
fmt.Printf("mainLoop: unhandled message %T: %v\n", msg, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle channel open messages from the remote side.
|
|
||||||
func (c *ClientConn) handleChanOpen(msg *channelOpenMsg) {
|
|
||||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
|
||||||
c.sendConnectionFailed(msg.PeersId)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg.ChanType {
|
|
||||||
case "forwarded-tcpip":
|
|
||||||
laddr, rest, ok := parseTCPAddr(msg.TypeSpecificData)
|
|
||||||
if !ok {
|
|
||||||
// invalid request
|
|
||||||
c.sendConnectionFailed(msg.PeersId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
l, ok := c.forwardList.lookup(*laddr)
|
|
||||||
if !ok {
|
|
||||||
// TODO: print on a more structured log.
|
|
||||||
fmt.Println("could not find forward list entry for", laddr)
|
|
||||||
// Section 7.2, implementations MUST reject spurious incoming
|
|
||||||
// connections.
|
|
||||||
c.sendConnectionFailed(msg.PeersId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
raddr, rest, ok := parseTCPAddr(rest)
|
|
||||||
if !ok {
|
|
||||||
// invalid request
|
|
||||||
c.sendConnectionFailed(msg.PeersId)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
ch := c.newChan(c.transport)
|
|
||||||
ch.remoteId = msg.PeersId
|
|
||||||
ch.remoteWin.add(msg.PeersWindow)
|
|
||||||
ch.maxPacket = msg.MaxPacketSize
|
|
||||||
|
|
||||||
m := channelOpenConfirmMsg{
|
|
||||||
PeersId: ch.remoteId,
|
|
||||||
MyId: ch.localId,
|
|
||||||
MyWindow: channelWindowSize,
|
|
||||||
MaxPacketSize: channelMaxPacketSize,
|
|
||||||
}
|
|
||||||
|
|
||||||
c.transport.writePacket(marshal(msgChannelOpenConfirm, m))
|
|
||||||
l <- forward{ch, raddr}
|
|
||||||
default:
|
|
||||||
// unknown channel type
|
|
||||||
m := channelOpenFailureMsg{
|
|
||||||
PeersId: msg.PeersId,
|
|
||||||
Reason: UnknownChannelType,
|
|
||||||
Message: fmt.Sprintf("unknown channel type: %v", msg.ChanType),
|
|
||||||
Language: "en_US.UTF-8",
|
|
||||||
}
|
|
||||||
c.transport.writePacket(marshal(msgChannelOpenFailure, m))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendGlobalRequest sends a global request message as specified
|
|
||||||
// in RFC4254 section 4. To correctly synchronise messages, a lock
|
|
||||||
// is held internally until a response is returned.
|
|
||||||
func (c *ClientConn) sendGlobalRequest(m interface{}) (*globalRequestSuccessMsg, error) {
|
|
||||||
c.globalRequest.Lock()
|
|
||||||
defer c.globalRequest.Unlock()
|
|
||||||
if err := c.transport.writePacket(marshal(msgGlobalRequest, m)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
r := <-c.globalRequest.response
|
|
||||||
if r, ok := r.(*globalRequestSuccessMsg); ok {
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
return nil, errors.New("request failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// sendConnectionFailed rejects an incoming channel identified
|
|
||||||
// by remoteId.
|
|
||||||
func (c *ClientConn) sendConnectionFailed(remoteId uint32) error {
|
|
||||||
m := channelOpenFailureMsg{
|
|
||||||
PeersId: remoteId,
|
|
||||||
Reason: ConnectionFailed,
|
|
||||||
Message: "invalid request",
|
|
||||||
Language: "en_US.UTF-8",
|
|
||||||
}
|
|
||||||
return c.transport.writePacket(marshal(msgChannelOpenFailure, m))
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseTCPAddr parses the originating address from the remote into a *net.TCPAddr.
|
|
||||||
// RFC 4254 section 7.2 is mute on what to do if parsing fails but the forwardlist
|
|
||||||
// requires a valid *net.TCPAddr to operate, so we enforce that restriction here.
|
|
||||||
func parseTCPAddr(b []byte) (*net.TCPAddr, []byte, bool) {
|
|
||||||
addr, b, ok := parseString(b)
|
|
||||||
if !ok {
|
|
||||||
return nil, b, false
|
|
||||||
}
|
|
||||||
port, b, ok := parseUint32(b)
|
|
||||||
if !ok {
|
|
||||||
return nil, b, false
|
|
||||||
}
|
|
||||||
ip := net.ParseIP(string(addr))
|
|
||||||
if ip == nil {
|
|
||||||
return nil, b, false
|
|
||||||
}
|
|
||||||
return &net.TCPAddr{IP: ip, Port: int(port)}, b, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial connects to the given network address using net.Dial and
|
|
||||||
// then initiates a SSH handshake, returning the resulting client connection.
|
|
||||||
func Dial(network, addr string, config *ClientConfig) (*ClientConn, error) {
|
|
||||||
conn, err := net.Dial(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return clientWithAddress(conn, addr, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ClientConfig structure is used to configure a ClientConn. After one has
|
|
||||||
// been passed to an SSH function it must not be modified.
|
|
||||||
type ClientConfig struct {
|
|
||||||
// Rand provides the source of entropy for key exchange. If Rand is
|
|
||||||
// nil, the cryptographic random reader in package crypto/rand will
|
|
||||||
// be used.
|
|
||||||
Rand io.Reader
|
|
||||||
|
|
||||||
// The username to authenticate.
|
|
||||||
User string
|
|
||||||
|
|
||||||
// A slice of ClientAuth methods. Only the first instance
|
|
||||||
// of a particular RFC 4252 method will be used during authentication.
|
|
||||||
Auth []ClientAuth
|
|
||||||
|
|
||||||
// HostKeyChecker, if not nil, is called during the cryptographic
|
|
||||||
// handshake to validate the server's host key. A nil HostKeyChecker
|
|
||||||
// implies that all host keys are accepted.
|
|
||||||
HostKeyChecker HostKeyChecker
|
|
||||||
|
|
||||||
// Cryptographic-related configuration.
|
|
||||||
Crypto CryptoConfig
|
|
||||||
|
|
||||||
// The identification string that will be used for the connection.
|
|
||||||
// If empty, a reasonable default is used.
|
|
||||||
ClientVersion string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ClientConfig) rand() io.Reader {
|
|
||||||
if c.Rand == nil {
|
|
||||||
return rand.Reader
|
|
||||||
}
|
|
||||||
return c.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thread safe channel list.
|
|
||||||
type chanList struct {
|
|
||||||
// protects concurrent access to chans
|
|
||||||
sync.Mutex
|
|
||||||
// chans are indexed by the local id of the channel, clientChan.localId.
|
|
||||||
// The PeersId value of messages received by ClientConn.mainLoop is
|
|
||||||
// used to locate the right local clientChan in this slice.
|
|
||||||
chans []*clientChan
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allocate a new ClientChan with the next avail local id.
|
|
||||||
func (c *chanList) newChan(p packetConn) *clientChan {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
for i := range c.chans {
|
|
||||||
if c.chans[i] == nil {
|
|
||||||
ch := newClientChan(p, uint32(i))
|
|
||||||
c.chans[i] = ch
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i := len(c.chans)
|
|
||||||
ch := newClientChan(p, uint32(i))
|
|
||||||
c.chans = append(c.chans, ch)
|
|
||||||
return ch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chanList) getChan(id uint32) (*clientChan, bool) {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
if id >= uint32(len(c.chans)) {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
return c.chans[id], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chanList) remove(id uint32) {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
c.chans[id] = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *chanList) closeAll() {
|
|
||||||
c.Lock()
|
|
||||||
defer c.Unlock()
|
|
||||||
|
|
||||||
for _, ch := range c.chans {
|
|
||||||
if ch == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ch.Close()
|
|
||||||
close(ch.msg)
|
|
||||||
}
|
|
||||||
}
|
|
509
vendor/code.google.com/p/go.crypto/ssh/client_auth.go
generated
vendored
509
vendor/code.google.com/p/go.crypto/ssh/client_auth.go
generated
vendored
@ -1,509 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
)
|
|
||||||
|
|
||||||
// authenticate authenticates with the remote server. See RFC 4252.
|
|
||||||
func (c *ClientConn) authenticate() error {
|
|
||||||
// initiate user auth session
|
|
||||||
if err := c.transport.writePacket(marshal(msgServiceRequest, serviceRequestMsg{serviceUserAuth})); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
packet, err := c.transport.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var serviceAccept serviceAcceptMsg
|
|
||||||
if err := unmarshal(&serviceAccept, packet, msgServiceAccept); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// during the authentication phase the client first attempts the "none" method
|
|
||||||
// then any untried methods suggested by the server.
|
|
||||||
tried, remain := make(map[string]bool), make(map[string]bool)
|
|
||||||
for auth := ClientAuth(new(noneAuth)); auth != nil; {
|
|
||||||
ok, methods, err := auth.auth(c.transport.sessionID, c.config.User, c.transport, c.config.rand())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ok {
|
|
||||||
// success
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
tried[auth.method()] = true
|
|
||||||
delete(remain, auth.method())
|
|
||||||
for _, meth := range methods {
|
|
||||||
if tried[meth] {
|
|
||||||
// if we've tried meth already, skip it.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
remain[meth] = true
|
|
||||||
}
|
|
||||||
auth = nil
|
|
||||||
for _, a := range c.config.Auth {
|
|
||||||
if remain[a.method()] {
|
|
||||||
auth = a
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return fmt.Errorf("ssh: unable to authenticate, attempted methods %v, no supported methods remain", keys(tried))
|
|
||||||
}
|
|
||||||
|
|
||||||
func keys(m map[string]bool) (s []string) {
|
|
||||||
for k := range m {
|
|
||||||
s = append(s, k)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// HostKeyChecker represents a database of known server host keys.
|
|
||||||
type HostKeyChecker interface {
|
|
||||||
// Check is called during the handshake to check server's
|
|
||||||
// public key for unexpected changes. The hostKey argument is
|
|
||||||
// in SSH wire format. It can be parsed using
|
|
||||||
// ssh.ParsePublicKey. The address before DNS resolution is
|
|
||||||
// passed in the addr argument, so the key can also be checked
|
|
||||||
// against the hostname.
|
|
||||||
Check(addr string, remote net.Addr, algorithm string, hostKey []byte) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ClientAuth represents an instance of an RFC 4252 authentication method.
|
|
||||||
type ClientAuth interface {
|
|
||||||
// auth authenticates user over transport t.
|
|
||||||
// Returns true if authentication is successful.
|
|
||||||
// If authentication is not successful, a []string of alternative
|
|
||||||
// method names is returned.
|
|
||||||
auth(session []byte, user string, p packetConn, rand io.Reader) (bool, []string, error)
|
|
||||||
|
|
||||||
// method returns the RFC 4252 method name.
|
|
||||||
method() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// "none" authentication, RFC 4252 section 5.2.
|
|
||||||
type noneAuth int
|
|
||||||
|
|
||||||
func (n *noneAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
if err := c.writePacket(marshal(msgUserAuthRequest, userAuthRequestMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: "none",
|
|
||||||
})); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *noneAuth) method() string {
|
|
||||||
return "none"
|
|
||||||
}
|
|
||||||
|
|
||||||
// "password" authentication, RFC 4252 Section 8.
|
|
||||||
type passwordAuth struct {
|
|
||||||
ClientPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *passwordAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
type passwordAuthMsg struct {
|
|
||||||
User string
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
Reply bool
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
pw, err := p.Password(user)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.writePacket(marshal(msgUserAuthRequest, passwordAuthMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: "password",
|
|
||||||
Reply: false,
|
|
||||||
Password: pw,
|
|
||||||
})); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return handleAuthResponse(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *passwordAuth) method() string {
|
|
||||||
return "password"
|
|
||||||
}
|
|
||||||
|
|
||||||
// A ClientPassword implements access to a client's passwords.
|
|
||||||
type ClientPassword interface {
|
|
||||||
// Password returns the password to use for user.
|
|
||||||
Password(user string) (password string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientAuthPassword returns a ClientAuth using password authentication.
|
|
||||||
func ClientAuthPassword(impl ClientPassword) ClientAuth {
|
|
||||||
return &passwordAuth{impl}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientKeyring implements access to a client key ring.
|
|
||||||
type ClientKeyring interface {
|
|
||||||
// Key returns the i'th Publickey, or nil if no key exists at i.
|
|
||||||
Key(i int) (key PublicKey, err error)
|
|
||||||
|
|
||||||
// Sign returns a signature of the given data using the i'th key
|
|
||||||
// and the supplied random source.
|
|
||||||
Sign(i int, rand io.Reader, data []byte) (sig []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// "publickey" authentication, RFC 4252 Section 7.
|
|
||||||
type publickeyAuth struct {
|
|
||||||
ClientKeyring
|
|
||||||
}
|
|
||||||
|
|
||||||
type publickeyAuthMsg struct {
|
|
||||||
User string
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
// HasSig indicates to the receiver packet that the auth request is signed and
|
|
||||||
// should be used for authentication of the request.
|
|
||||||
HasSig bool
|
|
||||||
Algoname string
|
|
||||||
Pubkey string
|
|
||||||
// Sig is defined as []byte so marshal will exclude it during validateKey
|
|
||||||
Sig []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *publickeyAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
// Authentication is performed in two stages. The first stage sends an
|
|
||||||
// enquiry to test if each key is acceptable to the remote. The second
|
|
||||||
// stage attempts to authenticate with the valid keys obtained in the
|
|
||||||
// first stage.
|
|
||||||
|
|
||||||
var index int
|
|
||||||
// a map of public keys to their index in the keyring
|
|
||||||
validKeys := make(map[int]PublicKey)
|
|
||||||
for {
|
|
||||||
key, err := p.Key(index)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
if key == nil {
|
|
||||||
// no more keys in the keyring
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, err := p.validateKey(key, user, c); ok {
|
|
||||||
validKeys[index] = key
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
|
|
||||||
// methods that may continue if this auth is not successful.
|
|
||||||
var methods []string
|
|
||||||
for i, key := range validKeys {
|
|
||||||
pubkey := MarshalPublicKey(key)
|
|
||||||
algoname := key.PublicKeyAlgo()
|
|
||||||
data := buildDataSignedForAuth(session, userAuthRequestMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: p.method(),
|
|
||||||
}, []byte(algoname), pubkey)
|
|
||||||
sigBlob, err := p.Sign(i, rand, data)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
// manually wrap the serialized signature in a string
|
|
||||||
s := serializeSignature(key.PublicKeyAlgo(), sigBlob)
|
|
||||||
sig := make([]byte, stringLength(len(s)))
|
|
||||||
marshalString(sig, s)
|
|
||||||
msg := publickeyAuthMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: p.method(),
|
|
||||||
HasSig: true,
|
|
||||||
Algoname: algoname,
|
|
||||||
Pubkey: string(pubkey),
|
|
||||||
Sig: sig,
|
|
||||||
}
|
|
||||||
p := marshal(msgUserAuthRequest, msg)
|
|
||||||
if err := c.writePacket(p); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
success, methods, err := handleAuthResponse(c)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
if success {
|
|
||||||
return success, methods, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, methods, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateKey validates the key provided it is acceptable to the server.
|
|
||||||
func (p *publickeyAuth) validateKey(key PublicKey, user string, c packetConn) (bool, error) {
|
|
||||||
pubkey := MarshalPublicKey(key)
|
|
||||||
algoname := key.PublicKeyAlgo()
|
|
||||||
msg := publickeyAuthMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: p.method(),
|
|
||||||
HasSig: false,
|
|
||||||
Algoname: algoname,
|
|
||||||
Pubkey: string(pubkey),
|
|
||||||
}
|
|
||||||
if err := c.writePacket(marshal(msgUserAuthRequest, msg)); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return p.confirmKeyAck(key, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *publickeyAuth) confirmKeyAck(key PublicKey, c packetConn) (bool, error) {
|
|
||||||
pubkey := MarshalPublicKey(key)
|
|
||||||
algoname := key.PublicKeyAlgo()
|
|
||||||
|
|
||||||
for {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
switch packet[0] {
|
|
||||||
case msgUserAuthBanner:
|
|
||||||
// TODO(gpaul): add callback to present the banner to the user
|
|
||||||
case msgUserAuthPubKeyOk:
|
|
||||||
msg := userAuthPubKeyOkMsg{}
|
|
||||||
if err := unmarshal(&msg, packet, msgUserAuthPubKeyOk); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if msg.Algo != algoname || msg.PubKey != string(pubkey) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
case msgUserAuthFailure:
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
return false, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *publickeyAuth) method() string {
|
|
||||||
return "publickey"
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientAuthKeyring returns a ClientAuth using public key authentication.
|
|
||||||
func ClientAuthKeyring(impl ClientKeyring) ClientAuth {
|
|
||||||
return &publickeyAuth{impl}
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleAuthResponse returns whether the preceding authentication request succeeded
|
|
||||||
// along with a list of remaining authentication methods to try next and
|
|
||||||
// an error if an unexpected response was received.
|
|
||||||
func handleAuthResponse(c packetConn) (bool, []string, error) {
|
|
||||||
for {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch packet[0] {
|
|
||||||
case msgUserAuthBanner:
|
|
||||||
// TODO: add callback to present the banner to the user
|
|
||||||
case msgUserAuthFailure:
|
|
||||||
msg := userAuthFailureMsg{}
|
|
||||||
if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
return false, msg.Methods, nil
|
|
||||||
case msgUserAuthSuccess:
|
|
||||||
return true, nil, nil
|
|
||||||
case msgDisconnect:
|
|
||||||
return false, nil, io.EOF
|
|
||||||
default:
|
|
||||||
return false, nil, UnexpectedMessageError{msgUserAuthSuccess, packet[0]}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientAuthAgent returns a ClientAuth using public key authentication via
|
|
||||||
// an agent.
|
|
||||||
func ClientAuthAgent(agent *AgentClient) ClientAuth {
|
|
||||||
return ClientAuthKeyring(&agentKeyring{agent: agent})
|
|
||||||
}
|
|
||||||
|
|
||||||
// agentKeyring implements ClientKeyring.
|
|
||||||
type agentKeyring struct {
|
|
||||||
agent *AgentClient
|
|
||||||
keys []*AgentKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kr *agentKeyring) Key(i int) (key PublicKey, err error) {
|
|
||||||
if kr.keys == nil {
|
|
||||||
if kr.keys, err = kr.agent.RequestIdentities(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if i >= len(kr.keys) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return kr.keys[i].Key()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kr *agentKeyring) Sign(i int, rand io.Reader, data []byte) (sig []byte, err error) {
|
|
||||||
var key PublicKey
|
|
||||||
if key, err = kr.Key(i); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if key == nil {
|
|
||||||
return nil, errors.New("ssh: key index out of range")
|
|
||||||
}
|
|
||||||
if sig, err = kr.agent.SignRequest(key, data); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal the signature.
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
if _, sig, ok = parseString(sig); !ok {
|
|
||||||
return nil, errors.New("ssh: malformed signature response from agent")
|
|
||||||
}
|
|
||||||
if sig, _, ok = parseString(sig); !ok {
|
|
||||||
return nil, errors.New("ssh: malformed signature response from agent")
|
|
||||||
}
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientKeyboardInteractive should prompt the user for the given
|
|
||||||
// questions.
|
|
||||||
type ClientKeyboardInteractive interface {
|
|
||||||
// Challenge should print the questions, optionally disabling
|
|
||||||
// echoing (eg. for passwords), and return all the answers.
|
|
||||||
// Challenge may be called multiple times in a single
|
|
||||||
// session. After successful authentication, the server may
|
|
||||||
// send a challenge with no questions, for which the user and
|
|
||||||
// instruction messages should be printed. RFC 4256 section
|
|
||||||
// 3.3 details how the UI should behave for both CLI and
|
|
||||||
// GUI environments.
|
|
||||||
Challenge(user, instruction string, questions []string, echos []bool) ([]string, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClientAuthKeyboardInteractive returns a ClientAuth using a
|
|
||||||
// prompt/response sequence controlled by the server.
|
|
||||||
func ClientAuthKeyboardInteractive(impl ClientKeyboardInteractive) ClientAuth {
|
|
||||||
return &keyboardInteractiveAuth{impl}
|
|
||||||
}
|
|
||||||
|
|
||||||
type keyboardInteractiveAuth struct {
|
|
||||||
ClientKeyboardInteractive
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keyboardInteractiveAuth) method() string {
|
|
||||||
return "keyboard-interactive"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *keyboardInteractiveAuth) auth(session []byte, user string, c packetConn, rand io.Reader) (bool, []string, error) {
|
|
||||||
type initiateMsg struct {
|
|
||||||
User string
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
Language string
|
|
||||||
Submethods string
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.writePacket(marshal(msgUserAuthRequest, initiateMsg{
|
|
||||||
User: user,
|
|
||||||
Service: serviceSSH,
|
|
||||||
Method: "keyboard-interactive",
|
|
||||||
})); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// like handleAuthResponse, but with less options.
|
|
||||||
switch packet[0] {
|
|
||||||
case msgUserAuthBanner:
|
|
||||||
// TODO: Print banners during userauth.
|
|
||||||
continue
|
|
||||||
case msgUserAuthInfoRequest:
|
|
||||||
// OK
|
|
||||||
case msgUserAuthFailure:
|
|
||||||
var msg userAuthFailureMsg
|
|
||||||
if err := unmarshal(&msg, packet, msgUserAuthFailure); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
return false, msg.Methods, nil
|
|
||||||
case msgUserAuthSuccess:
|
|
||||||
return true, nil, nil
|
|
||||||
default:
|
|
||||||
return false, nil, UnexpectedMessageError{msgUserAuthInfoRequest, packet[0]}
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg userAuthInfoRequestMsg
|
|
||||||
if err := unmarshal(&msg, packet, packet[0]); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Manually unpack the prompt/echo pairs.
|
|
||||||
rest := msg.Prompts
|
|
||||||
var prompts []string
|
|
||||||
var echos []bool
|
|
||||||
for i := 0; i < int(msg.NumPrompts); i++ {
|
|
||||||
prompt, r, ok := parseString(rest)
|
|
||||||
if !ok || len(r) == 0 {
|
|
||||||
return false, nil, errors.New("ssh: prompt format error")
|
|
||||||
}
|
|
||||||
prompts = append(prompts, string(prompt))
|
|
||||||
echos = append(echos, r[0] != 0)
|
|
||||||
rest = r[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(rest) != 0 {
|
|
||||||
return false, nil, fmt.Errorf("ssh: junk following message %q", rest)
|
|
||||||
}
|
|
||||||
|
|
||||||
answers, err := k.Challenge(msg.User, msg.Instruction, prompts, echos)
|
|
||||||
if err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(answers) != len(prompts) {
|
|
||||||
return false, nil, errors.New("ssh: not enough answers from keyboard-interactive callback")
|
|
||||||
}
|
|
||||||
responseLength := 1 + 4
|
|
||||||
for _, a := range answers {
|
|
||||||
responseLength += stringLength(len(a))
|
|
||||||
}
|
|
||||||
serialized := make([]byte, responseLength)
|
|
||||||
p := serialized
|
|
||||||
p[0] = msgUserAuthInfoResponse
|
|
||||||
p = p[1:]
|
|
||||||
p = marshalUint32(p, uint32(len(answers)))
|
|
||||||
for _, a := range answers {
|
|
||||||
p = marshalString(p, []byte(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
|
||||||
return false, nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
352
vendor/code.google.com/p/go.crypto/ssh/common.go
generated
vendored
352
vendor/code.google.com/p/go.crypto/ssh/common.go
generated
vendored
@ -1,352 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"fmt"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
_ "crypto/sha1"
|
|
||||||
_ "crypto/sha256"
|
|
||||||
_ "crypto/sha512"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These are string constants in the SSH protocol.
|
|
||||||
const (
|
|
||||||
compressionNone = "none"
|
|
||||||
serviceUserAuth = "ssh-userauth"
|
|
||||||
serviceSSH = "ssh-connection"
|
|
||||||
)
|
|
||||||
|
|
||||||
var supportedKexAlgos = []string{
|
|
||||||
kexAlgoECDH256, kexAlgoECDH384, kexAlgoECDH521,
|
|
||||||
kexAlgoDH14SHA1, kexAlgoDH1SHA1,
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedHostKeyAlgos = []string{
|
|
||||||
KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
|
||||||
KeyAlgoRSA, KeyAlgoDSA,
|
|
||||||
}
|
|
||||||
|
|
||||||
var supportedCompressions = []string{compressionNone}
|
|
||||||
|
|
||||||
// hashFuncs keeps the mapping of supported algorithms to their respective
|
|
||||||
// hashes needed for signature verification.
|
|
||||||
var hashFuncs = map[string]crypto.Hash{
|
|
||||||
KeyAlgoRSA: crypto.SHA1,
|
|
||||||
KeyAlgoDSA: crypto.SHA1,
|
|
||||||
KeyAlgoECDSA256: crypto.SHA256,
|
|
||||||
KeyAlgoECDSA384: crypto.SHA384,
|
|
||||||
KeyAlgoECDSA521: crypto.SHA512,
|
|
||||||
CertAlgoRSAv01: crypto.SHA1,
|
|
||||||
CertAlgoDSAv01: crypto.SHA1,
|
|
||||||
CertAlgoECDSA256v01: crypto.SHA256,
|
|
||||||
CertAlgoECDSA384v01: crypto.SHA384,
|
|
||||||
CertAlgoECDSA521v01: crypto.SHA512,
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnexpectedMessageError results when the SSH message that we received didn't
|
|
||||||
// match what we wanted.
|
|
||||||
type UnexpectedMessageError struct {
|
|
||||||
expected, got uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UnexpectedMessageError) Error() string {
|
|
||||||
return fmt.Sprintf("ssh: unexpected message type %d (expected %d)", u.got, u.expected)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseError results from a malformed SSH message.
|
|
||||||
type ParseError struct {
|
|
||||||
msgType uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p ParseError) Error() string {
|
|
||||||
return fmt.Sprintf("ssh: parse error in message type %d", p.msgType)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCommonAlgorithm(clientAlgos []string, serverAlgos []string) (commonAlgo string, ok bool) {
|
|
||||||
for _, clientAlgo := range clientAlgos {
|
|
||||||
for _, serverAlgo := range serverAlgos {
|
|
||||||
if clientAlgo == serverAlgo {
|
|
||||||
return clientAlgo, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func findCommonCipher(clientCiphers []string, serverCiphers []string) (commonCipher string, ok bool) {
|
|
||||||
for _, clientCipher := range clientCiphers {
|
|
||||||
for _, serverCipher := range serverCiphers {
|
|
||||||
// reject the cipher if we have no cipherModes definition
|
|
||||||
if clientCipher == serverCipher && cipherModes[clientCipher] != nil {
|
|
||||||
return clientCipher, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type algorithms struct {
|
|
||||||
kex string
|
|
||||||
hostKey string
|
|
||||||
wCipher string
|
|
||||||
rCipher string
|
|
||||||
rMAC string
|
|
||||||
wMAC string
|
|
||||||
rCompression string
|
|
||||||
wCompression string
|
|
||||||
}
|
|
||||||
|
|
||||||
func findAgreedAlgorithms(clientKexInit, serverKexInit *kexInitMsg) (algs *algorithms) {
|
|
||||||
var ok bool
|
|
||||||
result := &algorithms{}
|
|
||||||
result.kex, ok = findCommonAlgorithm(clientKexInit.KexAlgos, serverKexInit.KexAlgos)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.hostKey, ok = findCommonAlgorithm(clientKexInit.ServerHostKeyAlgos, serverKexInit.ServerHostKeyAlgos)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.wCipher, ok = findCommonCipher(clientKexInit.CiphersClientServer, serverKexInit.CiphersClientServer)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.rCipher, ok = findCommonCipher(clientKexInit.CiphersServerClient, serverKexInit.CiphersServerClient)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.wMAC, ok = findCommonAlgorithm(clientKexInit.MACsClientServer, serverKexInit.MACsClientServer)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.rMAC, ok = findCommonAlgorithm(clientKexInit.MACsServerClient, serverKexInit.MACsServerClient)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.wCompression, ok = findCommonAlgorithm(clientKexInit.CompressionClientServer, serverKexInit.CompressionClientServer)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result.rCompression, ok = findCommonAlgorithm(clientKexInit.CompressionServerClient, serverKexInit.CompressionServerClient)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cryptographic configuration common to both ServerConfig and ClientConfig.
|
|
||||||
type CryptoConfig struct {
|
|
||||||
// The allowed key exchanges algorithms. If unspecified then a
|
|
||||||
// default set of algorithms is used.
|
|
||||||
KeyExchanges []string
|
|
||||||
|
|
||||||
// The allowed cipher algorithms. If unspecified then DefaultCipherOrder is
|
|
||||||
// used.
|
|
||||||
Ciphers []string
|
|
||||||
|
|
||||||
// The allowed MAC algorithms. If unspecified then DefaultMACOrder is used.
|
|
||||||
MACs []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CryptoConfig) ciphers() []string {
|
|
||||||
if c.Ciphers == nil {
|
|
||||||
return DefaultCipherOrder
|
|
||||||
}
|
|
||||||
return c.Ciphers
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CryptoConfig) kexes() []string {
|
|
||||||
if c.KeyExchanges == nil {
|
|
||||||
return defaultKeyExchangeOrder
|
|
||||||
}
|
|
||||||
return c.KeyExchanges
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *CryptoConfig) macs() []string {
|
|
||||||
if c.MACs == nil {
|
|
||||||
return DefaultMACOrder
|
|
||||||
}
|
|
||||||
return c.MACs
|
|
||||||
}
|
|
||||||
|
|
||||||
// serialize a signed slice according to RFC 4254 6.6. The name should
|
|
||||||
// be a key type name, rather than a cert type name.
|
|
||||||
func serializeSignature(name string, sig []byte) []byte {
|
|
||||||
length := stringLength(len(name))
|
|
||||||
length += stringLength(len(sig))
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
r := marshalString(ret, []byte(name))
|
|
||||||
r = marshalString(r, sig)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalPublicKey serializes a supported key or certificate for use
|
|
||||||
// by the SSH wire protocol. It can be used for comparison with the
|
|
||||||
// pubkey argument of ServerConfig's PublicKeyCallback as well as for
|
|
||||||
// generating an authorized_keys or host_keys file.
|
|
||||||
func MarshalPublicKey(key PublicKey) []byte {
|
|
||||||
// See also RFC 4253 6.6.
|
|
||||||
algoname := key.PublicKeyAlgo()
|
|
||||||
blob := key.Marshal()
|
|
||||||
|
|
||||||
length := stringLength(len(algoname))
|
|
||||||
length += len(blob)
|
|
||||||
ret := make([]byte, length)
|
|
||||||
r := marshalString(ret, []byte(algoname))
|
|
||||||
copy(r, blob)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// pubAlgoToPrivAlgo returns the private key algorithm format name that
|
|
||||||
// corresponds to a given public key algorithm format name. For most
|
|
||||||
// public keys, the private key algorithm name is the same. For some
|
|
||||||
// situations, such as openssh certificates, the private key algorithm and
|
|
||||||
// public key algorithm names differ. This accounts for those situations.
|
|
||||||
func pubAlgoToPrivAlgo(pubAlgo string) string {
|
|
||||||
switch pubAlgo {
|
|
||||||
case CertAlgoRSAv01:
|
|
||||||
return KeyAlgoRSA
|
|
||||||
case CertAlgoDSAv01:
|
|
||||||
return KeyAlgoDSA
|
|
||||||
case CertAlgoECDSA256v01:
|
|
||||||
return KeyAlgoECDSA256
|
|
||||||
case CertAlgoECDSA384v01:
|
|
||||||
return KeyAlgoECDSA384
|
|
||||||
case CertAlgoECDSA521v01:
|
|
||||||
return KeyAlgoECDSA521
|
|
||||||
}
|
|
||||||
return pubAlgo
|
|
||||||
}
|
|
||||||
|
|
||||||
// buildDataSignedForAuth returns the data that is signed in order to prove
|
|
||||||
// possession of a private key. See RFC 4252, section 7.
|
|
||||||
func buildDataSignedForAuth(sessionId []byte, req userAuthRequestMsg, algo, pubKey []byte) []byte {
|
|
||||||
user := []byte(req.User)
|
|
||||||
service := []byte(req.Service)
|
|
||||||
method := []byte(req.Method)
|
|
||||||
|
|
||||||
length := stringLength(len(sessionId))
|
|
||||||
length += 1
|
|
||||||
length += stringLength(len(user))
|
|
||||||
length += stringLength(len(service))
|
|
||||||
length += stringLength(len(method))
|
|
||||||
length += 1
|
|
||||||
length += stringLength(len(algo))
|
|
||||||
length += stringLength(len(pubKey))
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
r := marshalString(ret, sessionId)
|
|
||||||
r[0] = msgUserAuthRequest
|
|
||||||
r = r[1:]
|
|
||||||
r = marshalString(r, user)
|
|
||||||
r = marshalString(r, service)
|
|
||||||
r = marshalString(r, method)
|
|
||||||
r[0] = 1
|
|
||||||
r = r[1:]
|
|
||||||
r = marshalString(r, algo)
|
|
||||||
r = marshalString(r, pubKey)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
// safeString sanitises s according to RFC 4251, section 9.2.
|
|
||||||
// All control characters except tab, carriage return and newline are
|
|
||||||
// replaced by 0x20.
|
|
||||||
func safeString(s string) string {
|
|
||||||
out := []byte(s)
|
|
||||||
for i, c := range out {
|
|
||||||
if c < 0x20 && c != 0xd && c != 0xa && c != 0x9 {
|
|
||||||
out[i] = 0x20
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(out)
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendU16(buf []byte, n uint16) []byte {
|
|
||||||
return append(buf, byte(n>>8), byte(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendU32(buf []byte, n uint32) []byte {
|
|
||||||
return append(buf, byte(n>>24), byte(n>>16), byte(n>>8), byte(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendInt(buf []byte, n int) []byte {
|
|
||||||
return appendU32(buf, uint32(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendString(buf []byte, s string) []byte {
|
|
||||||
buf = appendU32(buf, uint32(len(s)))
|
|
||||||
buf = append(buf, s...)
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
func appendBool(buf []byte, b bool) []byte {
|
|
||||||
if b {
|
|
||||||
buf = append(buf, 1)
|
|
||||||
} else {
|
|
||||||
buf = append(buf, 0)
|
|
||||||
}
|
|
||||||
return buf
|
|
||||||
}
|
|
||||||
|
|
||||||
// newCond is a helper to hide the fact that there is no usable zero
|
|
||||||
// value for sync.Cond.
|
|
||||||
func newCond() *sync.Cond { return sync.NewCond(new(sync.Mutex)) }
|
|
||||||
|
|
||||||
// window represents the buffer available to clients
|
|
||||||
// wishing to write to a channel.
|
|
||||||
type window struct {
|
|
||||||
*sync.Cond
|
|
||||||
win uint32 // RFC 4254 5.2 says the window size can grow to 2^32-1
|
|
||||||
}
|
|
||||||
|
|
||||||
// add adds win to the amount of window available
|
|
||||||
// for consumers.
|
|
||||||
func (w *window) add(win uint32) bool {
|
|
||||||
// a zero sized window adjust is a noop.
|
|
||||||
if win == 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
w.L.Lock()
|
|
||||||
if w.win+win < win {
|
|
||||||
w.L.Unlock()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
w.win += win
|
|
||||||
// It is unusual that multiple goroutines would be attempting to reserve
|
|
||||||
// window space, but not guaranteed. Use broadcast to notify all waiters
|
|
||||||
// that additional window is available.
|
|
||||||
w.Broadcast()
|
|
||||||
w.L.Unlock()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// reserve reserves win from the available window capacity.
|
|
||||||
// If no capacity remains, reserve will block. reserve may
|
|
||||||
// return less than requested.
|
|
||||||
func (w *window) reserve(win uint32) uint32 {
|
|
||||||
w.L.Lock()
|
|
||||||
for w.win == 0 {
|
|
||||||
w.Wait()
|
|
||||||
}
|
|
||||||
if w.win < win {
|
|
||||||
win = w.win
|
|
||||||
}
|
|
||||||
w.win -= win
|
|
||||||
w.L.Unlock()
|
|
||||||
return win
|
|
||||||
}
|
|
19
vendor/code.google.com/p/go.crypto/ssh/doc.go
generated
vendored
19
vendor/code.google.com/p/go.crypto/ssh/doc.go
generated
vendored
@ -1,19 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package ssh implements an SSH client and server.
|
|
||||||
|
|
||||||
SSH is a transport security protocol, an authentication protocol and a
|
|
||||||
family of application protocols. The most typical application level
|
|
||||||
protocol is a remote shell and this is specifically implemented. However,
|
|
||||||
the multiplexed nature of SSH is exposed to users that wish to support
|
|
||||||
others.
|
|
||||||
|
|
||||||
References:
|
|
||||||
[PROTOCOL.certkeys]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.certkeys
|
|
||||||
[PROTOCOL.agent]: http://www.openbsd.org/cgi-bin/cvsweb/src/usr.bin/ssh/PROTOCOL.agent
|
|
||||||
[SSH-PARAMETERS]: http://www.iana.org/assignments/ssh-parameters/ssh-parameters.xml#ssh-parameters-1
|
|
||||||
*/
|
|
||||||
package ssh
|
|
386
vendor/code.google.com/p/go.crypto/ssh/kex.go
generated
vendored
386
vendor/code.google.com/p/go.crypto/ssh/kex.go
generated
vendored
@ -1,386 +0,0 @@
|
|||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rand"
|
|
||||||
"errors"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
kexAlgoDH1SHA1 = "diffie-hellman-group1-sha1"
|
|
||||||
kexAlgoDH14SHA1 = "diffie-hellman-group14-sha1"
|
|
||||||
kexAlgoECDH256 = "ecdh-sha2-nistp256"
|
|
||||||
kexAlgoECDH384 = "ecdh-sha2-nistp384"
|
|
||||||
kexAlgoECDH521 = "ecdh-sha2-nistp521"
|
|
||||||
)
|
|
||||||
|
|
||||||
// kexResult captures the outcome of a key exchange.
|
|
||||||
type kexResult struct {
|
|
||||||
// Session hash. See also RFC 4253, section 8.
|
|
||||||
H []byte
|
|
||||||
|
|
||||||
// Shared secret. See also RFC 4253, section 8.
|
|
||||||
K []byte
|
|
||||||
|
|
||||||
// Host key as hashed into H
|
|
||||||
HostKey []byte
|
|
||||||
|
|
||||||
// Signature of H
|
|
||||||
Signature []byte
|
|
||||||
|
|
||||||
// A cryptographic hash function that matches the security
|
|
||||||
// level of the key exchange algorithm. It is used for
|
|
||||||
// calculating H, and for deriving keys from H and K.
|
|
||||||
Hash crypto.Hash
|
|
||||||
|
|
||||||
// The session ID, which is the first H computed. This is used
|
|
||||||
// to signal data inside transport.
|
|
||||||
SessionID []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// handshakeMagics contains data that is always included in the
|
|
||||||
// session hash.
|
|
||||||
type handshakeMagics struct {
|
|
||||||
clientVersion, serverVersion []byte
|
|
||||||
clientKexInit, serverKexInit []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *handshakeMagics) write(w io.Writer) {
|
|
||||||
writeString(w, m.clientVersion)
|
|
||||||
writeString(w, m.serverVersion)
|
|
||||||
writeString(w, m.clientKexInit)
|
|
||||||
writeString(w, m.serverKexInit)
|
|
||||||
}
|
|
||||||
|
|
||||||
// kexAlgorithm abstracts different key exchange algorithms.
|
|
||||||
type kexAlgorithm interface {
|
|
||||||
// Server runs server-side key agreement, signing the result
|
|
||||||
// with a hostkey.
|
|
||||||
Server(p packetConn, rand io.Reader, magics *handshakeMagics, s Signer) (*kexResult, error)
|
|
||||||
|
|
||||||
// Client runs the client-side key agreement. Caller is
|
|
||||||
// responsible for verifying the host key signature.
|
|
||||||
Client(p packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// dhGroup is a multiplicative group suitable for implementing Diffie-Hellman key agreement.
|
|
||||||
type dhGroup struct {
|
|
||||||
g, p *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *dhGroup) diffieHellman(theirPublic, myPrivate *big.Int) (*big.Int, error) {
|
|
||||||
if theirPublic.Sign() <= 0 || theirPublic.Cmp(group.p) >= 0 {
|
|
||||||
return nil, errors.New("ssh: DH parameter out of bounds")
|
|
||||||
}
|
|
||||||
return new(big.Int).Exp(theirPublic, myPrivate, group.p), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *dhGroup) Client(c packetConn, randSource io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
|
||||||
hashFunc := crypto.SHA1
|
|
||||||
|
|
||||||
x, err := rand.Int(randSource, group.p)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
X := new(big.Int).Exp(group.g, x, group.p)
|
|
||||||
kexDHInit := kexDHInitMsg{
|
|
||||||
X: X,
|
|
||||||
}
|
|
||||||
if err := c.writePacket(marshal(msgKexDHInit, kexDHInit)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var kexDHReply kexDHReplyMsg
|
|
||||||
if err = unmarshal(&kexDHReply, packet, msgKexDHReply); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kInt, err := group.diffieHellman(kexDHReply.Y, x)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
h := hashFunc.New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, kexDHReply.HostKey)
|
|
||||||
writeInt(h, X)
|
|
||||||
writeInt(h, kexDHReply.Y)
|
|
||||||
K := make([]byte, intLength(kInt))
|
|
||||||
marshalInt(K, kInt)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
return &kexResult{
|
|
||||||
H: h.Sum(nil),
|
|
||||||
K: K,
|
|
||||||
HostKey: kexDHReply.HostKey,
|
|
||||||
Signature: kexDHReply.Signature,
|
|
||||||
Hash: crypto.SHA1,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (group *dhGroup) Server(c packetConn, randSource io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
|
||||||
hashFunc := crypto.SHA1
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var kexDHInit kexDHInitMsg
|
|
||||||
if err = unmarshal(&kexDHInit, packet, msgKexDHInit); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
y, err := rand.Int(randSource, group.p)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
Y := new(big.Int).Exp(group.g, y, group.p)
|
|
||||||
kInt, err := group.diffieHellman(kexDHInit.X, y)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostKeyBytes := MarshalPublicKey(priv.PublicKey())
|
|
||||||
|
|
||||||
h := hashFunc.New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, hostKeyBytes)
|
|
||||||
writeInt(h, kexDHInit.X)
|
|
||||||
writeInt(h, Y)
|
|
||||||
|
|
||||||
K := make([]byte, intLength(kInt))
|
|
||||||
marshalInt(K, kInt)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
H := h.Sum(nil)
|
|
||||||
|
|
||||||
// H is already a hash, but the hostkey signing will apply its
|
|
||||||
// own key-specific hash algorithm.
|
|
||||||
sig, err := signAndMarshal(priv, randSource, H)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kexDHReply := kexDHReplyMsg{
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Y: Y,
|
|
||||||
Signature: sig,
|
|
||||||
}
|
|
||||||
packet = marshal(msgKexDHReply, kexDHReply)
|
|
||||||
|
|
||||||
err = c.writePacket(packet)
|
|
||||||
return &kexResult{
|
|
||||||
H: H,
|
|
||||||
K: K,
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Signature: sig,
|
|
||||||
Hash: crypto.SHA1,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ecdh performs Elliptic Curve Diffie-Hellman key exchange as
|
|
||||||
// described in RFC 5656, section 4.
|
|
||||||
type ecdh struct {
|
|
||||||
curve elliptic.Curve
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kex *ecdh) Client(c packetConn, rand io.Reader, magics *handshakeMagics) (*kexResult, error) {
|
|
||||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
kexInit := kexECDHInitMsg{
|
|
||||||
ClientPubKey: elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y),
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized := marshal(msgKexECDHInit, kexInit)
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var reply kexECDHReplyMsg
|
|
||||||
if err = unmarshal(&reply, packet, msgKexECDHReply); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
x, y, err := unmarshalECKey(kex.curve, reply.EphemeralPubKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate shared secret
|
|
||||||
secret, _ := kex.curve.ScalarMult(x, y, ephKey.D.Bytes())
|
|
||||||
|
|
||||||
h := ecHash(kex.curve).New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, reply.HostKey)
|
|
||||||
writeString(h, kexInit.ClientPubKey)
|
|
||||||
writeString(h, reply.EphemeralPubKey)
|
|
||||||
K := make([]byte, intLength(secret))
|
|
||||||
marshalInt(K, secret)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
return &kexResult{
|
|
||||||
H: h.Sum(nil),
|
|
||||||
K: K,
|
|
||||||
HostKey: reply.HostKey,
|
|
||||||
Signature: reply.Signature,
|
|
||||||
Hash: ecHash(kex.curve),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshalECKey parses and checks an EC key.
|
|
||||||
func unmarshalECKey(curve elliptic.Curve, pubkey []byte) (x, y *big.Int, err error) {
|
|
||||||
x, y = elliptic.Unmarshal(curve, pubkey)
|
|
||||||
if x == nil {
|
|
||||||
return nil, nil, errors.New("ssh: elliptic.Unmarshal failure")
|
|
||||||
}
|
|
||||||
if !validateECPublicKey(curve, x, y) {
|
|
||||||
return nil, nil, errors.New("ssh: public key not on curve")
|
|
||||||
}
|
|
||||||
return x, y, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateECPublicKey checks that the point is a valid public key for
|
|
||||||
// the given curve. See [SEC1], 3.2.2
|
|
||||||
func validateECPublicKey(curve elliptic.Curve, x, y *big.Int) bool {
|
|
||||||
if x.Sign() == 0 && y.Sign() == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if x.Cmp(curve.Params().P) >= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if y.Cmp(curve.Params().P) >= 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if !curve.IsOnCurve(x, y) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't check if N * PubKey == 0, since
|
|
||||||
//
|
|
||||||
// - the NIST curves have cofactor = 1, so this is implicit.
|
|
||||||
// (We don't foresee an implementation that supports non NIST
|
|
||||||
// curves)
|
|
||||||
//
|
|
||||||
// - for ephemeral keys, we don't need to worry about small
|
|
||||||
// subgroup attacks.
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (kex *ecdh) Server(c packetConn, rand io.Reader, magics *handshakeMagics, priv Signer) (result *kexResult, err error) {
|
|
||||||
packet, err := c.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var kexECDHInit kexECDHInitMsg
|
|
||||||
if err = unmarshal(&kexECDHInit, packet, msgKexECDHInit); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
clientX, clientY, err := unmarshalECKey(kex.curve, kexECDHInit.ClientPubKey)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// We could cache this key across multiple users/multiple
|
|
||||||
// connection attempts, but the benefit is small. OpenSSH
|
|
||||||
// generates a new key for each incoming connection.
|
|
||||||
ephKey, err := ecdsa.GenerateKey(kex.curve, rand)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
hostKeyBytes := MarshalPublicKey(priv.PublicKey())
|
|
||||||
|
|
||||||
serializedEphKey := elliptic.Marshal(kex.curve, ephKey.PublicKey.X, ephKey.PublicKey.Y)
|
|
||||||
|
|
||||||
// generate shared secret
|
|
||||||
secret, _ := kex.curve.ScalarMult(clientX, clientY, ephKey.D.Bytes())
|
|
||||||
|
|
||||||
h := ecHash(kex.curve).New()
|
|
||||||
magics.write(h)
|
|
||||||
writeString(h, hostKeyBytes)
|
|
||||||
writeString(h, kexECDHInit.ClientPubKey)
|
|
||||||
writeString(h, serializedEphKey)
|
|
||||||
|
|
||||||
K := make([]byte, intLength(secret))
|
|
||||||
marshalInt(K, secret)
|
|
||||||
h.Write(K)
|
|
||||||
|
|
||||||
H := h.Sum(nil)
|
|
||||||
|
|
||||||
// H is already a hash, but the hostkey signing will apply its
|
|
||||||
// own key-specific hash algorithm.
|
|
||||||
sig, err := signAndMarshal(priv, rand, H)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
reply := kexECDHReplyMsg{
|
|
||||||
EphemeralPubKey: serializedEphKey,
|
|
||||||
HostKey: hostKeyBytes,
|
|
||||||
Signature: sig,
|
|
||||||
}
|
|
||||||
|
|
||||||
serialized := marshal(msgKexECDHReply, reply)
|
|
||||||
if err := c.writePacket(serialized); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &kexResult{
|
|
||||||
H: H,
|
|
||||||
K: K,
|
|
||||||
HostKey: reply.HostKey,
|
|
||||||
Signature: sig,
|
|
||||||
Hash: ecHash(kex.curve),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var kexAlgoMap = map[string]kexAlgorithm{}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// This is the group called diffie-hellman-group1-sha1 in RFC
|
|
||||||
// 4253 and Oakley Group 2 in RFC 2409.
|
|
||||||
p, _ := new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE65381FFFFFFFFFFFFFFFF", 16)
|
|
||||||
kexAlgoMap[kexAlgoDH1SHA1] = &dhGroup{
|
|
||||||
g: new(big.Int).SetInt64(2),
|
|
||||||
p: p,
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the group called diffie-hellman-group14-sha1 in RFC
|
|
||||||
// 4253 and Oakley Group 14 in RFC 3526.
|
|
||||||
p, _ = new(big.Int).SetString("FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA18217C32905E462E36CE3BE39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9DE2BCBF6955817183995497CEA956AE515D2261898FA051015728E5A8AACAA68FFFFFFFFFFFFFFFF", 16)
|
|
||||||
|
|
||||||
kexAlgoMap[kexAlgoDH14SHA1] = &dhGroup{
|
|
||||||
g: new(big.Int).SetInt64(2),
|
|
||||||
p: p,
|
|
||||||
}
|
|
||||||
|
|
||||||
kexAlgoMap[kexAlgoECDH521] = &ecdh{elliptic.P521()}
|
|
||||||
kexAlgoMap[kexAlgoECDH384] = &ecdh{elliptic.P384()}
|
|
||||||
kexAlgoMap[kexAlgoECDH256] = &ecdh{elliptic.P256()}
|
|
||||||
}
|
|
609
vendor/code.google.com/p/go.crypto/ssh/keys.go
generated
vendored
609
vendor/code.google.com/p/go.crypto/ssh/keys.go
generated
vendored
@ -1,609 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto"
|
|
||||||
"crypto/dsa"
|
|
||||||
"crypto/ecdsa"
|
|
||||||
"crypto/elliptic"
|
|
||||||
"crypto/rsa"
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/pem"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These constants represent the algorithm names for key types supported by this
|
|
||||||
// package.
|
|
||||||
const (
|
|
||||||
KeyAlgoRSA = "ssh-rsa"
|
|
||||||
KeyAlgoDSA = "ssh-dss"
|
|
||||||
KeyAlgoECDSA256 = "ecdsa-sha2-nistp256"
|
|
||||||
KeyAlgoECDSA384 = "ecdsa-sha2-nistp384"
|
|
||||||
KeyAlgoECDSA521 = "ecdsa-sha2-nistp521"
|
|
||||||
)
|
|
||||||
|
|
||||||
// parsePubKey parses a public key of the given algorithm.
|
|
||||||
// Use ParsePublicKey for keys with prepended algorithm.
|
|
||||||
func parsePubKey(in []byte, algo string) (pubKey PublicKey, rest []byte, ok bool) {
|
|
||||||
switch algo {
|
|
||||||
case KeyAlgoRSA:
|
|
||||||
return parseRSA(in)
|
|
||||||
case KeyAlgoDSA:
|
|
||||||
return parseDSA(in)
|
|
||||||
case KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521:
|
|
||||||
return parseECDSA(in)
|
|
||||||
case CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
|
||||||
return parseOpenSSHCertV01(in, algo)
|
|
||||||
}
|
|
||||||
return nil, nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseAuthorizedKey parses a public key in OpenSSH authorized_keys format
|
|
||||||
// (see sshd(8) manual page) once the options and key type fields have been
|
|
||||||
// removed.
|
|
||||||
func parseAuthorizedKey(in []byte) (out PublicKey, comment string, ok bool) {
|
|
||||||
in = bytes.TrimSpace(in)
|
|
||||||
|
|
||||||
i := bytes.IndexAny(in, " \t")
|
|
||||||
if i == -1 {
|
|
||||||
i = len(in)
|
|
||||||
}
|
|
||||||
base64Key := in[:i]
|
|
||||||
|
|
||||||
key := make([]byte, base64.StdEncoding.DecodedLen(len(base64Key)))
|
|
||||||
n, err := base64.StdEncoding.Decode(key, base64Key)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key = key[:n]
|
|
||||||
out, _, ok = ParsePublicKey(key)
|
|
||||||
if !ok {
|
|
||||||
return nil, "", false
|
|
||||||
}
|
|
||||||
comment = string(bytes.TrimSpace(in[i:]))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseAuthorizedKeys parses a public key from an authorized_keys
|
|
||||||
// file used in OpenSSH according to the sshd(8) manual page.
|
|
||||||
func ParseAuthorizedKey(in []byte) (out PublicKey, comment string, options []string, rest []byte, ok bool) {
|
|
||||||
for len(in) > 0 {
|
|
||||||
end := bytes.IndexByte(in, '\n')
|
|
||||||
if end != -1 {
|
|
||||||
rest = in[end+1:]
|
|
||||||
in = in[:end]
|
|
||||||
} else {
|
|
||||||
rest = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
end = bytes.IndexByte(in, '\r')
|
|
||||||
if end != -1 {
|
|
||||||
in = in[:end]
|
|
||||||
}
|
|
||||||
|
|
||||||
in = bytes.TrimSpace(in)
|
|
||||||
if len(in) == 0 || in[0] == '#' {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
i := bytes.IndexAny(in, " \t")
|
|
||||||
if i == -1 {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if out, comment, ok = parseAuthorizedKey(in[i:]); ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// No key type recognised. Maybe there's an options field at
|
|
||||||
// the beginning.
|
|
||||||
var b byte
|
|
||||||
inQuote := false
|
|
||||||
var candidateOptions []string
|
|
||||||
optionStart := 0
|
|
||||||
for i, b = range in {
|
|
||||||
isEnd := !inQuote && (b == ' ' || b == '\t')
|
|
||||||
if (b == ',' && !inQuote) || isEnd {
|
|
||||||
if i-optionStart > 0 {
|
|
||||||
candidateOptions = append(candidateOptions, string(in[optionStart:i]))
|
|
||||||
}
|
|
||||||
optionStart = i + 1
|
|
||||||
}
|
|
||||||
if isEnd {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if b == '"' && (i == 0 || (i > 0 && in[i-1] != '\\')) {
|
|
||||||
inQuote = !inQuote
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for i < len(in) && (in[i] == ' ' || in[i] == '\t') {
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
if i == len(in) {
|
|
||||||
// Invalid line: unmatched quote
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
in = in[i:]
|
|
||||||
i = bytes.IndexAny(in, " \t")
|
|
||||||
if i == -1 {
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if out, comment, ok = parseAuthorizedKey(in[i:]); ok {
|
|
||||||
options = candidateOptions
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
in = rest
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePublicKey parses an SSH public key formatted for use in
|
|
||||||
// the SSH wire protocol according to RFC 4253, section 6.6.
|
|
||||||
func ParsePublicKey(in []byte) (out PublicKey, rest []byte, ok bool) {
|
|
||||||
algo, in, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsePubKey(in, string(algo))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalAuthorizedKey returns a byte stream suitable for inclusion
|
|
||||||
// in an OpenSSH authorized_keys file following the format specified
|
|
||||||
// in the sshd(8) manual page.
|
|
||||||
func MarshalAuthorizedKey(key PublicKey) []byte {
|
|
||||||
b := &bytes.Buffer{}
|
|
||||||
b.WriteString(key.PublicKeyAlgo())
|
|
||||||
b.WriteByte(' ')
|
|
||||||
e := base64.NewEncoder(base64.StdEncoding, b)
|
|
||||||
e.Write(MarshalPublicKey(key))
|
|
||||||
e.Close()
|
|
||||||
b.WriteByte('\n')
|
|
||||||
return b.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// PublicKey is an abstraction of different types of public keys.
|
|
||||||
type PublicKey interface {
|
|
||||||
// PrivateKeyAlgo returns the name of the encryption system.
|
|
||||||
PrivateKeyAlgo() string
|
|
||||||
|
|
||||||
// PublicKeyAlgo returns the algorithm for the public key,
|
|
||||||
// which may be different from PrivateKeyAlgo for certificates.
|
|
||||||
PublicKeyAlgo() string
|
|
||||||
|
|
||||||
// Marshal returns the serialized key data in SSH wire format,
|
|
||||||
// without the name prefix. Callers should typically use
|
|
||||||
// MarshalPublicKey().
|
|
||||||
Marshal() []byte
|
|
||||||
|
|
||||||
// Verify that sig is a signature on the given data using this
|
|
||||||
// key. This function will hash the data appropriately first.
|
|
||||||
Verify(data []byte, sigBlob []byte) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Signer is can create signatures that verify against a public key.
|
|
||||||
type Signer interface {
|
|
||||||
// PublicKey returns an associated PublicKey instance.
|
|
||||||
PublicKey() PublicKey
|
|
||||||
|
|
||||||
// Sign returns raw signature for the given data. This method
|
|
||||||
// will apply the hash specified for the keytype to the data.
|
|
||||||
Sign(rand io.Reader, data []byte) ([]byte, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type rsaPublicKey rsa.PublicKey
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) PrivateKeyAlgo() string {
|
|
||||||
return "ssh-rsa"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) PublicKeyAlgo() string {
|
|
||||||
return r.PrivateKeyAlgo()
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseRSA parses an RSA key according to RFC 4253, section 6.6.
|
|
||||||
func parseRSA(in []byte) (out PublicKey, rest []byte, ok bool) {
|
|
||||||
key := new(rsa.PublicKey)
|
|
||||||
|
|
||||||
bigE, in, ok := parseInt(in)
|
|
||||||
if !ok || bigE.BitLen() > 24 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
e := bigE.Int64()
|
|
||||||
if e < 3 || e&1 == 0 {
|
|
||||||
ok = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
key.E = int(e)
|
|
||||||
|
|
||||||
if key.N, in, ok = parseInt(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
return (*rsaPublicKey)(key), in, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) Marshal() []byte {
|
|
||||||
// See RFC 4253, section 6.6.
|
|
||||||
e := new(big.Int).SetInt64(int64(r.E))
|
|
||||||
length := intLength(e)
|
|
||||||
length += intLength(r.N)
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
rest := marshalInt(ret, e)
|
|
||||||
marshalInt(rest, r.N)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPublicKey) Verify(data []byte, sig []byte) bool {
|
|
||||||
h := crypto.SHA1.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
return rsa.VerifyPKCS1v15((*rsa.PublicKey)(r), crypto.SHA1, digest, sig) == nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type rsaPrivateKey struct {
|
|
||||||
*rsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPrivateKey) PublicKey() PublicKey {
|
|
||||||
return (*rsaPublicKey)(&r.PrivateKey.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *rsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) {
|
|
||||||
h := crypto.SHA1.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
return rsa.SignPKCS1v15(rand, r.PrivateKey, crypto.SHA1, digest)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dsaPublicKey dsa.PublicKey
|
|
||||||
|
|
||||||
func (r *dsaPublicKey) PrivateKeyAlgo() string {
|
|
||||||
return "ssh-dss"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *dsaPublicKey) PublicKeyAlgo() string {
|
|
||||||
return r.PrivateKeyAlgo()
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDSA parses an DSA key according to RFC 4253, section 6.6.
|
|
||||||
func parseDSA(in []byte) (out PublicKey, rest []byte, ok bool) {
|
|
||||||
key := new(dsa.PublicKey)
|
|
||||||
|
|
||||||
if key.P, in, ok = parseInt(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Q, in, ok = parseInt(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.G, in, ok = parseInt(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if key.Y, in, ok = parseInt(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = true
|
|
||||||
return (*dsaPublicKey)(key), in, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *dsaPublicKey) Marshal() []byte {
|
|
||||||
// See RFC 4253, section 6.6.
|
|
||||||
length := intLength(r.P)
|
|
||||||
length += intLength(r.Q)
|
|
||||||
length += intLength(r.G)
|
|
||||||
length += intLength(r.Y)
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
rest := marshalInt(ret, r.P)
|
|
||||||
rest = marshalInt(rest, r.Q)
|
|
||||||
rest = marshalInt(rest, r.G)
|
|
||||||
marshalInt(rest, r.Y)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPublicKey) Verify(data []byte, sigBlob []byte) bool {
|
|
||||||
h := crypto.SHA1.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
|
|
||||||
// Per RFC 4253, section 6.6,
|
|
||||||
// The value for 'dss_signature_blob' is encoded as a string containing
|
|
||||||
// r, followed by s (which are 160-bit integers, without lengths or
|
|
||||||
// padding, unsigned, and in network byte order).
|
|
||||||
// For DSS purposes, sig.Blob should be exactly 40 bytes in length.
|
|
||||||
if len(sigBlob) != 40 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
r := new(big.Int).SetBytes(sigBlob[:20])
|
|
||||||
s := new(big.Int).SetBytes(sigBlob[20:])
|
|
||||||
return dsa.Verify((*dsa.PublicKey)(k), digest, r, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type dsaPrivateKey struct {
|
|
||||||
*dsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPrivateKey) PublicKey() PublicKey {
|
|
||||||
return (*dsaPublicKey)(&k.PrivateKey.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *dsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) {
|
|
||||||
h := crypto.SHA1.New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
r, s, err := dsa.Sign(rand, k.PrivateKey, digest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := make([]byte, 40)
|
|
||||||
copy(sig[:20], r.Bytes())
|
|
||||||
copy(sig[20:], s.Bytes())
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type ecdsaPublicKey ecdsa.PublicKey
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) PrivateKeyAlgo() string {
|
|
||||||
return "ecdsa-sha2-" + key.nistID()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) nistID() string {
|
|
||||||
switch key.Params().BitSize {
|
|
||||||
case 256:
|
|
||||||
return "nistp256"
|
|
||||||
case 384:
|
|
||||||
return "nistp384"
|
|
||||||
case 521:
|
|
||||||
return "nistp521"
|
|
||||||
}
|
|
||||||
panic("ssh: unsupported ecdsa key size")
|
|
||||||
}
|
|
||||||
|
|
||||||
func supportedEllipticCurve(curve elliptic.Curve) bool {
|
|
||||||
return (curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521())
|
|
||||||
}
|
|
||||||
|
|
||||||
// ecHash returns the hash to match the given elliptic curve, see RFC
|
|
||||||
// 5656, section 6.2.1
|
|
||||||
func ecHash(curve elliptic.Curve) crypto.Hash {
|
|
||||||
bitSize := curve.Params().BitSize
|
|
||||||
switch {
|
|
||||||
case bitSize <= 256:
|
|
||||||
return crypto.SHA256
|
|
||||||
case bitSize <= 384:
|
|
||||||
return crypto.SHA384
|
|
||||||
}
|
|
||||||
return crypto.SHA512
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) PublicKeyAlgo() string {
|
|
||||||
return key.PrivateKeyAlgo()
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseECDSA parses an ECDSA key according to RFC 5656, section 3.1.
|
|
||||||
func parseECDSA(in []byte) (out PublicKey, rest []byte, ok bool) {
|
|
||||||
var identifier []byte
|
|
||||||
if identifier, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key := new(ecdsa.PublicKey)
|
|
||||||
|
|
||||||
switch string(identifier) {
|
|
||||||
case "nistp256":
|
|
||||||
key.Curve = elliptic.P256()
|
|
||||||
case "nistp384":
|
|
||||||
key.Curve = elliptic.P384()
|
|
||||||
case "nistp521":
|
|
||||||
key.Curve = elliptic.P521()
|
|
||||||
default:
|
|
||||||
ok = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var keyBytes []byte
|
|
||||||
if keyBytes, in, ok = parseString(in); !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
key.X, key.Y = elliptic.Unmarshal(key.Curve, keyBytes)
|
|
||||||
if key.X == nil || key.Y == nil {
|
|
||||||
ok = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return (*ecdsaPublicKey)(key), in, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Marshal() []byte {
|
|
||||||
// See RFC 5656, section 3.1.
|
|
||||||
keyBytes := elliptic.Marshal(key.Curve, key.X, key.Y)
|
|
||||||
|
|
||||||
ID := key.nistID()
|
|
||||||
length := stringLength(len(ID))
|
|
||||||
length += stringLength(len(keyBytes))
|
|
||||||
|
|
||||||
ret := make([]byte, length)
|
|
||||||
r := marshalString(ret, []byte(ID))
|
|
||||||
r = marshalString(r, keyBytes)
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (key *ecdsaPublicKey) Verify(data []byte, sigBlob []byte) bool {
|
|
||||||
h := ecHash(key.Curve).New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
|
|
||||||
// Per RFC 5656, section 3.1.2,
|
|
||||||
// The ecdsa_signature_blob value has the following specific encoding:
|
|
||||||
// mpint r
|
|
||||||
// mpint s
|
|
||||||
r, rest, ok := parseInt(sigBlob)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
s, rest, ok := parseInt(rest)
|
|
||||||
if !ok || len(rest) > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return ecdsa.Verify((*ecdsa.PublicKey)(key), digest, r, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ecdsaPrivateKey struct {
|
|
||||||
*ecdsa.PrivateKey
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecdsaPrivateKey) PublicKey() PublicKey {
|
|
||||||
return (*ecdsaPublicKey)(&k.PrivateKey.PublicKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k *ecdsaPrivateKey) Sign(rand io.Reader, data []byte) ([]byte, error) {
|
|
||||||
h := ecHash(k.PrivateKey.PublicKey.Curve).New()
|
|
||||||
h.Write(data)
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
r, s, err := ecdsa.Sign(rand, k.PrivateKey, digest)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
sig := make([]byte, intLength(r)+intLength(s))
|
|
||||||
rest := marshalInt(sig, r)
|
|
||||||
marshalInt(rest, s)
|
|
||||||
return sig, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPrivateKey takes a pointer to rsa, dsa or ecdsa PrivateKey
|
|
||||||
// returns a corresponding Signer instance. EC keys should use P256,
|
|
||||||
// P384 or P521.
|
|
||||||
func NewSignerFromKey(k interface{}) (Signer, error) {
|
|
||||||
var sshKey Signer
|
|
||||||
switch t := k.(type) {
|
|
||||||
case *rsa.PrivateKey:
|
|
||||||
sshKey = &rsaPrivateKey{t}
|
|
||||||
case *dsa.PrivateKey:
|
|
||||||
sshKey = &dsaPrivateKey{t}
|
|
||||||
case *ecdsa.PrivateKey:
|
|
||||||
if !supportedEllipticCurve(t.Curve) {
|
|
||||||
return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
|
|
||||||
}
|
|
||||||
|
|
||||||
sshKey = &ecdsaPrivateKey{t}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
|
|
||||||
}
|
|
||||||
return sshKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewPublicKey takes a pointer to rsa, dsa or ecdsa PublicKey
|
|
||||||
// and returns a corresponding ssh PublicKey instance. EC keys should use P256, P384 or P521.
|
|
||||||
func NewPublicKey(k interface{}) (PublicKey, error) {
|
|
||||||
var sshKey PublicKey
|
|
||||||
switch t := k.(type) {
|
|
||||||
case *rsa.PublicKey:
|
|
||||||
sshKey = (*rsaPublicKey)(t)
|
|
||||||
case *ecdsa.PublicKey:
|
|
||||||
if !supportedEllipticCurve(t.Curve) {
|
|
||||||
return nil, errors.New("ssh: only P256, P384 and P521 EC keys are supported.")
|
|
||||||
}
|
|
||||||
sshKey = (*ecdsaPublicKey)(t)
|
|
||||||
case *dsa.PublicKey:
|
|
||||||
sshKey = (*dsaPublicKey)(t)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %T", k)
|
|
||||||
}
|
|
||||||
return sshKey, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePublicKey parses a PEM encoded private key. It supports
|
|
||||||
// PKCS#1, RSA, DSA and ECDSA private keys.
|
|
||||||
func ParsePrivateKey(pemBytes []byte) (Signer, error) {
|
|
||||||
block, _ := pem.Decode(pemBytes)
|
|
||||||
if block == nil {
|
|
||||||
return nil, errors.New("ssh: no key found")
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawkey interface{}
|
|
||||||
switch block.Type {
|
|
||||||
case "RSA PRIVATE KEY":
|
|
||||||
rsa, err := x509.ParsePKCS1PrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawkey = rsa
|
|
||||||
case "EC PRIVATE KEY":
|
|
||||||
ec, err := x509.ParseECPrivateKey(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawkey = ec
|
|
||||||
case "DSA PRIVATE KEY":
|
|
||||||
ec, err := parseDSAPrivate(block.Bytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
rawkey = ec
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("ssh: unsupported key type %q", block.Type)
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewSignerFromKey(rawkey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseDSAPrivate parses a DSA key in ASN.1 DER encoding, as
|
|
||||||
// documented in the OpenSSL DSA manpage.
|
|
||||||
// TODO(hanwen): move this in to crypto/x509 after the Go 1.2 freeze.
|
|
||||||
func parseDSAPrivate(p []byte) (*dsa.PrivateKey, error) {
|
|
||||||
k := struct {
|
|
||||||
Version int
|
|
||||||
P *big.Int
|
|
||||||
Q *big.Int
|
|
||||||
G *big.Int
|
|
||||||
Priv *big.Int
|
|
||||||
Pub *big.Int
|
|
||||||
}{}
|
|
||||||
rest, err := asn1.Unmarshal(p, &k)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New("ssh: failed to parse DSA key: " + err.Error())
|
|
||||||
}
|
|
||||||
if len(rest) > 0 {
|
|
||||||
return nil, errors.New("ssh: garbage after DSA key")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &dsa.PrivateKey{
|
|
||||||
PublicKey: dsa.PublicKey{
|
|
||||||
Parameters: dsa.Parameters{
|
|
||||||
P: k.P,
|
|
||||||
Q: k.Q,
|
|
||||||
G: k.G,
|
|
||||||
},
|
|
||||||
Y: k.Priv,
|
|
||||||
},
|
|
||||||
X: k.Pub,
|
|
||||||
}, nil
|
|
||||||
}
|
|
58
vendor/code.google.com/p/go.crypto/ssh/mac.go
generated
vendored
58
vendor/code.google.com/p/go.crypto/ssh/mac.go
generated
vendored
@ -1,58 +0,0 @@
|
|||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
// Message authentication support
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/hmac"
|
|
||||||
"crypto/sha1"
|
|
||||||
"hash"
|
|
||||||
)
|
|
||||||
|
|
||||||
type macMode struct {
|
|
||||||
keySize int
|
|
||||||
new func(key []byte) hash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
// truncatingMAC wraps around a hash.Hash and truncates the output digest to
|
|
||||||
// a given size.
|
|
||||||
type truncatingMAC struct {
|
|
||||||
length int
|
|
||||||
hmac hash.Hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t truncatingMAC) Write(data []byte) (int, error) {
|
|
||||||
return t.hmac.Write(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t truncatingMAC) Sum(in []byte) []byte {
|
|
||||||
out := t.hmac.Sum(in)
|
|
||||||
return out[:len(in)+t.length]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t truncatingMAC) Reset() {
|
|
||||||
t.hmac.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t truncatingMAC) Size() int {
|
|
||||||
return t.length
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t truncatingMAC) BlockSize() int { return t.hmac.BlockSize() }
|
|
||||||
|
|
||||||
// Specifies a default set of MAC algorithms and a preference order.
|
|
||||||
// This is based on RFC 4253, section 6.4, with the removal of the
|
|
||||||
// hmac-md5 variants as they have reached the end of their useful life.
|
|
||||||
var DefaultMACOrder = []string{"hmac-sha1", "hmac-sha1-96"}
|
|
||||||
|
|
||||||
var macModes = map[string]*macMode{
|
|
||||||
"hmac-sha1": {20, func(key []byte) hash.Hash {
|
|
||||||
return hmac.New(sha1.New, key)
|
|
||||||
}},
|
|
||||||
"hmac-sha1-96": {20, func(key []byte) hash.Hash {
|
|
||||||
return truncatingMAC{12, hmac.New(sha1.New, key)}
|
|
||||||
}},
|
|
||||||
}
|
|
659
vendor/code.google.com/p/go.crypto/ssh/messages.go
generated
vendored
659
vendor/code.google.com/p/go.crypto/ssh/messages.go
generated
vendored
@ -1,659 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"io"
|
|
||||||
"math/big"
|
|
||||||
"reflect"
|
|
||||||
)
|
|
||||||
|
|
||||||
// These are SSH message type numbers. They are scattered around several
|
|
||||||
// documents but many were taken from [SSH-PARAMETERS].
|
|
||||||
const (
|
|
||||||
msgDisconnect = 1
|
|
||||||
msgIgnore = 2
|
|
||||||
msgUnimplemented = 3
|
|
||||||
msgDebug = 4
|
|
||||||
msgServiceRequest = 5
|
|
||||||
msgServiceAccept = 6
|
|
||||||
|
|
||||||
msgKexInit = 20
|
|
||||||
msgNewKeys = 21
|
|
||||||
|
|
||||||
// Diffie-Helman
|
|
||||||
msgKexDHInit = 30
|
|
||||||
msgKexDHReply = 31
|
|
||||||
|
|
||||||
msgKexECDHInit = 30
|
|
||||||
msgKexECDHReply = 31
|
|
||||||
|
|
||||||
// Standard authentication messages
|
|
||||||
msgUserAuthRequest = 50
|
|
||||||
msgUserAuthFailure = 51
|
|
||||||
msgUserAuthSuccess = 52
|
|
||||||
msgUserAuthBanner = 53
|
|
||||||
msgUserAuthPubKeyOk = 60
|
|
||||||
|
|
||||||
// Method specific messages
|
|
||||||
msgUserAuthInfoRequest = 60
|
|
||||||
msgUserAuthInfoResponse = 61
|
|
||||||
|
|
||||||
msgGlobalRequest = 80
|
|
||||||
msgRequestSuccess = 81
|
|
||||||
msgRequestFailure = 82
|
|
||||||
|
|
||||||
// Channel manipulation
|
|
||||||
msgChannelOpen = 90
|
|
||||||
msgChannelOpenConfirm = 91
|
|
||||||
msgChannelOpenFailure = 92
|
|
||||||
msgChannelWindowAdjust = 93
|
|
||||||
msgChannelData = 94
|
|
||||||
msgChannelExtendedData = 95
|
|
||||||
msgChannelEOF = 96
|
|
||||||
msgChannelClose = 97
|
|
||||||
msgChannelRequest = 98
|
|
||||||
msgChannelSuccess = 99
|
|
||||||
msgChannelFailure = 100
|
|
||||||
)
|
|
||||||
|
|
||||||
// SSH messages:
|
|
||||||
//
|
|
||||||
// These structures mirror the wire format of the corresponding SSH messages.
|
|
||||||
// They are marshaled using reflection with the marshal and unmarshal functions
|
|
||||||
// in this file. The only wrinkle is that a final member of type []byte with a
|
|
||||||
// ssh tag of "rest" receives the remainder of a packet when unmarshaling.
|
|
||||||
|
|
||||||
// See RFC 4253, section 11.1.
|
|
||||||
type disconnectMsg struct {
|
|
||||||
Reason uint32
|
|
||||||
Message string
|
|
||||||
Language string
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4253, section 7.1.
|
|
||||||
type kexInitMsg struct {
|
|
||||||
Cookie [16]byte
|
|
||||||
KexAlgos []string
|
|
||||||
ServerHostKeyAlgos []string
|
|
||||||
CiphersClientServer []string
|
|
||||||
CiphersServerClient []string
|
|
||||||
MACsClientServer []string
|
|
||||||
MACsServerClient []string
|
|
||||||
CompressionClientServer []string
|
|
||||||
CompressionServerClient []string
|
|
||||||
LanguagesClientServer []string
|
|
||||||
LanguagesServerClient []string
|
|
||||||
FirstKexFollows bool
|
|
||||||
Reserved uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4253, section 8.
|
|
||||||
type kexDHInitMsg struct {
|
|
||||||
X *big.Int
|
|
||||||
}
|
|
||||||
|
|
||||||
type kexECDHInitMsg struct {
|
|
||||||
ClientPubKey []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type kexECDHReplyMsg struct {
|
|
||||||
HostKey []byte
|
|
||||||
EphemeralPubKey []byte
|
|
||||||
Signature []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
type kexDHReplyMsg struct {
|
|
||||||
HostKey []byte
|
|
||||||
Y *big.Int
|
|
||||||
Signature []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4253, section 10.
|
|
||||||
type serviceRequestMsg struct {
|
|
||||||
Service string
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4253, section 10.
|
|
||||||
type serviceAcceptMsg struct {
|
|
||||||
Service string
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4252, section 5.
|
|
||||||
type userAuthRequestMsg struct {
|
|
||||||
User string
|
|
||||||
Service string
|
|
||||||
Method string
|
|
||||||
Payload []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4252, section 5.1
|
|
||||||
type userAuthFailureMsg struct {
|
|
||||||
Methods []string
|
|
||||||
PartialSuccess bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4256, section 3.2
|
|
||||||
type userAuthInfoRequestMsg struct {
|
|
||||||
User string
|
|
||||||
Instruction string
|
|
||||||
DeprecatedLanguage string
|
|
||||||
NumPrompts uint32
|
|
||||||
Prompts []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.1.
|
|
||||||
type channelOpenMsg struct {
|
|
||||||
ChanType string
|
|
||||||
PeersId uint32
|
|
||||||
PeersWindow uint32
|
|
||||||
MaxPacketSize uint32
|
|
||||||
TypeSpecificData []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.1.
|
|
||||||
type channelOpenConfirmMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
MyId uint32
|
|
||||||
MyWindow uint32
|
|
||||||
MaxPacketSize uint32
|
|
||||||
TypeSpecificData []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.1.
|
|
||||||
type channelOpenFailureMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
Reason RejectionReason
|
|
||||||
Message string
|
|
||||||
Language string
|
|
||||||
}
|
|
||||||
|
|
||||||
type channelRequestMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
Request string
|
|
||||||
WantReply bool
|
|
||||||
RequestSpecificData []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.4.
|
|
||||||
type channelRequestSuccessMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.4.
|
|
||||||
type channelRequestFailureMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.3
|
|
||||||
type channelCloseMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.3
|
|
||||||
type channelEOFMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 4
|
|
||||||
type globalRequestMsg struct {
|
|
||||||
Type string
|
|
||||||
WantReply bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 4
|
|
||||||
type globalRequestSuccessMsg struct {
|
|
||||||
Data []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 4
|
|
||||||
type globalRequestFailureMsg struct {
|
|
||||||
Data []byte `ssh:"rest"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4254, section 5.2
|
|
||||||
type windowAdjustMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
AdditionalBytes uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// See RFC 4252, section 7
|
|
||||||
type userAuthPubKeyOkMsg struct {
|
|
||||||
Algo string
|
|
||||||
PubKey string
|
|
||||||
}
|
|
||||||
|
|
||||||
// unmarshal parses the SSH wire data in packet into out using
|
|
||||||
// reflection. expectedType, if non-zero, is the SSH message type that
|
|
||||||
// the packet is expected to start with. unmarshal either returns nil
|
|
||||||
// on success, or a ParseError or UnexpectedMessageError on error.
|
|
||||||
func unmarshal(out interface{}, packet []byte, expectedType uint8) error {
|
|
||||||
if len(packet) == 0 {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
if expectedType > 0 {
|
|
||||||
if packet[0] != expectedType {
|
|
||||||
return UnexpectedMessageError{expectedType, packet[0]}
|
|
||||||
}
|
|
||||||
packet = packet[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
v := reflect.ValueOf(out).Elem()
|
|
||||||
structType := v.Type()
|
|
||||||
var ok bool
|
|
||||||
for i := 0; i < v.NumField(); i++ {
|
|
||||||
field := v.Field(i)
|
|
||||||
t := field.Type()
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
if len(packet) < 1 {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
field.SetBool(packet[0] != 0)
|
|
||||||
packet = packet[1:]
|
|
||||||
case reflect.Array:
|
|
||||||
if t.Elem().Kind() != reflect.Uint8 {
|
|
||||||
panic("array of non-uint8")
|
|
||||||
}
|
|
||||||
if len(packet) < t.Len() {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
for j, n := 0, t.Len(); j < n; j++ {
|
|
||||||
field.Index(j).Set(reflect.ValueOf(packet[j]))
|
|
||||||
}
|
|
||||||
packet = packet[t.Len():]
|
|
||||||
case reflect.Uint32:
|
|
||||||
var u32 uint32
|
|
||||||
if u32, packet, ok = parseUint32(packet); !ok {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
field.SetUint(uint64(u32))
|
|
||||||
case reflect.String:
|
|
||||||
var s []byte
|
|
||||||
if s, packet, ok = parseString(packet); !ok {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
field.SetString(string(s))
|
|
||||||
case reflect.Slice:
|
|
||||||
switch t.Elem().Kind() {
|
|
||||||
case reflect.Uint8:
|
|
||||||
if structType.Field(i).Tag.Get("ssh") == "rest" {
|
|
||||||
field.Set(reflect.ValueOf(packet))
|
|
||||||
packet = nil
|
|
||||||
} else {
|
|
||||||
var s []byte
|
|
||||||
if s, packet, ok = parseString(packet); !ok {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
field.Set(reflect.ValueOf(s))
|
|
||||||
}
|
|
||||||
case reflect.String:
|
|
||||||
var nl []string
|
|
||||||
if nl, packet, ok = parseNameList(packet); !ok {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
field.Set(reflect.ValueOf(nl))
|
|
||||||
default:
|
|
||||||
panic("slice of unknown type")
|
|
||||||
}
|
|
||||||
case reflect.Ptr:
|
|
||||||
if t == bigIntType {
|
|
||||||
var n *big.Int
|
|
||||||
if n, packet, ok = parseInt(packet); !ok {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
field.Set(reflect.ValueOf(n))
|
|
||||||
} else {
|
|
||||||
panic("pointer to unknown type")
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unknown type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(packet) != 0 {
|
|
||||||
return ParseError{expectedType}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// marshal serializes the message in msg. The given message type is
|
|
||||||
// prepended if it is non-zero.
|
|
||||||
func marshal(msgType uint8, msg interface{}) []byte {
|
|
||||||
out := make([]byte, 0, 64)
|
|
||||||
if msgType > 0 {
|
|
||||||
out = append(out, msgType)
|
|
||||||
}
|
|
||||||
|
|
||||||
v := reflect.ValueOf(msg)
|
|
||||||
for i, n := 0, v.NumField(); i < n; i++ {
|
|
||||||
field := v.Field(i)
|
|
||||||
switch t := field.Type(); t.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
var v uint8
|
|
||||||
if field.Bool() {
|
|
||||||
v = 1
|
|
||||||
}
|
|
||||||
out = append(out, v)
|
|
||||||
case reflect.Array:
|
|
||||||
if t.Elem().Kind() != reflect.Uint8 {
|
|
||||||
panic("array of non-uint8")
|
|
||||||
}
|
|
||||||
for j, l := 0, t.Len(); j < l; j++ {
|
|
||||||
out = append(out, uint8(field.Index(j).Uint()))
|
|
||||||
}
|
|
||||||
case reflect.Uint32:
|
|
||||||
out = appendU32(out, uint32(field.Uint()))
|
|
||||||
case reflect.String:
|
|
||||||
s := field.String()
|
|
||||||
out = appendInt(out, len(s))
|
|
||||||
out = append(out, s...)
|
|
||||||
case reflect.Slice:
|
|
||||||
switch t.Elem().Kind() {
|
|
||||||
case reflect.Uint8:
|
|
||||||
if v.Type().Field(i).Tag.Get("ssh") != "rest" {
|
|
||||||
out = appendInt(out, field.Len())
|
|
||||||
}
|
|
||||||
out = append(out, field.Bytes()...)
|
|
||||||
case reflect.String:
|
|
||||||
offset := len(out)
|
|
||||||
out = appendU32(out, 0)
|
|
||||||
if n := field.Len(); n > 0 {
|
|
||||||
for j := 0; j < n; j++ {
|
|
||||||
f := field.Index(j)
|
|
||||||
if j != 0 {
|
|
||||||
out = append(out, ',')
|
|
||||||
}
|
|
||||||
out = append(out, f.String()...)
|
|
||||||
}
|
|
||||||
// overwrite length value
|
|
||||||
binary.BigEndian.PutUint32(out[offset:], uint32(len(out)-offset-4))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("slice of unknown type")
|
|
||||||
}
|
|
||||||
case reflect.Ptr:
|
|
||||||
if t == bigIntType {
|
|
||||||
var n *big.Int
|
|
||||||
nValue := reflect.ValueOf(&n)
|
|
||||||
nValue.Elem().Set(field)
|
|
||||||
needed := intLength(n)
|
|
||||||
oldLength := len(out)
|
|
||||||
|
|
||||||
if cap(out)-len(out) < needed {
|
|
||||||
newOut := make([]byte, len(out), 2*(len(out)+needed))
|
|
||||||
copy(newOut, out)
|
|
||||||
out = newOut
|
|
||||||
}
|
|
||||||
out = out[:oldLength+needed]
|
|
||||||
marshalInt(out[oldLength:], n)
|
|
||||||
} else {
|
|
||||||
panic("pointer to unknown type")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
var bigOne = big.NewInt(1)
|
|
||||||
|
|
||||||
func parseString(in []byte) (out, rest []byte, ok bool) {
|
|
||||||
if len(in) < 4 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
length := binary.BigEndian.Uint32(in)
|
|
||||||
if uint32(len(in)) < 4+length {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out = in[4 : 4+length]
|
|
||||||
rest = in[4+length:]
|
|
||||||
ok = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
comma = []byte{','}
|
|
||||||
emptyNameList = []string{}
|
|
||||||
)
|
|
||||||
|
|
||||||
func parseNameList(in []byte) (out []string, rest []byte, ok bool) {
|
|
||||||
contents, rest, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if len(contents) == 0 {
|
|
||||||
out = emptyNameList
|
|
||||||
return
|
|
||||||
}
|
|
||||||
parts := bytes.Split(contents, comma)
|
|
||||||
out = make([]string, len(parts))
|
|
||||||
for i, part := range parts {
|
|
||||||
out[i] = string(part)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseInt(in []byte) (out *big.Int, rest []byte, ok bool) {
|
|
||||||
contents, rest, ok := parseString(in)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out = new(big.Int)
|
|
||||||
|
|
||||||
if len(contents) > 0 && contents[0]&0x80 == 0x80 {
|
|
||||||
// This is a negative number
|
|
||||||
notBytes := make([]byte, len(contents))
|
|
||||||
for i := range notBytes {
|
|
||||||
notBytes[i] = ^contents[i]
|
|
||||||
}
|
|
||||||
out.SetBytes(notBytes)
|
|
||||||
out.Add(out, bigOne)
|
|
||||||
out.Neg(out)
|
|
||||||
} else {
|
|
||||||
// Positive number
|
|
||||||
out.SetBytes(contents)
|
|
||||||
}
|
|
||||||
ok = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUint32(in []byte) (uint32, []byte, bool) {
|
|
||||||
if len(in) < 4 {
|
|
||||||
return 0, nil, false
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint32(in), in[4:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseUint64(in []byte) (uint64, []byte, bool) {
|
|
||||||
if len(in) < 8 {
|
|
||||||
return 0, nil, false
|
|
||||||
}
|
|
||||||
return binary.BigEndian.Uint64(in), in[8:], true
|
|
||||||
}
|
|
||||||
|
|
||||||
func nameListLength(namelist []string) int {
|
|
||||||
length := 4 /* uint32 length prefix */
|
|
||||||
for i, name := range namelist {
|
|
||||||
if i != 0 {
|
|
||||||
length++ /* comma */
|
|
||||||
}
|
|
||||||
length += len(name)
|
|
||||||
}
|
|
||||||
return length
|
|
||||||
}
|
|
||||||
|
|
||||||
func intLength(n *big.Int) int {
|
|
||||||
length := 4 /* length bytes */
|
|
||||||
if n.Sign() < 0 {
|
|
||||||
nMinus1 := new(big.Int).Neg(n)
|
|
||||||
nMinus1.Sub(nMinus1, bigOne)
|
|
||||||
bitLen := nMinus1.BitLen()
|
|
||||||
if bitLen%8 == 0 {
|
|
||||||
// The number will need 0xff padding
|
|
||||||
length++
|
|
||||||
}
|
|
||||||
length += (bitLen + 7) / 8
|
|
||||||
} else if n.Sign() == 0 {
|
|
||||||
// A zero is the zero length string
|
|
||||||
} else {
|
|
||||||
bitLen := n.BitLen()
|
|
||||||
if bitLen%8 == 0 {
|
|
||||||
// The number will need 0x00 padding
|
|
||||||
length++
|
|
||||||
}
|
|
||||||
length += (bitLen + 7) / 8
|
|
||||||
}
|
|
||||||
|
|
||||||
return length
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalUint32(to []byte, n uint32) []byte {
|
|
||||||
binary.BigEndian.PutUint32(to, n)
|
|
||||||
return to[4:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalUint64(to []byte, n uint64) []byte {
|
|
||||||
binary.BigEndian.PutUint64(to, n)
|
|
||||||
return to[8:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalInt(to []byte, n *big.Int) []byte {
|
|
||||||
lengthBytes := to
|
|
||||||
to = to[4:]
|
|
||||||
length := 0
|
|
||||||
|
|
||||||
if n.Sign() < 0 {
|
|
||||||
// A negative number has to be converted to two's-complement
|
|
||||||
// form. So we'll subtract 1 and invert. If the
|
|
||||||
// most-significant-bit isn't set then we'll need to pad the
|
|
||||||
// beginning with 0xff in order to keep the number negative.
|
|
||||||
nMinus1 := new(big.Int).Neg(n)
|
|
||||||
nMinus1.Sub(nMinus1, bigOne)
|
|
||||||
bytes := nMinus1.Bytes()
|
|
||||||
for i := range bytes {
|
|
||||||
bytes[i] ^= 0xff
|
|
||||||
}
|
|
||||||
if len(bytes) == 0 || bytes[0]&0x80 == 0 {
|
|
||||||
to[0] = 0xff
|
|
||||||
to = to[1:]
|
|
||||||
length++
|
|
||||||
}
|
|
||||||
nBytes := copy(to, bytes)
|
|
||||||
to = to[nBytes:]
|
|
||||||
length += nBytes
|
|
||||||
} else if n.Sign() == 0 {
|
|
||||||
// A zero is the zero length string
|
|
||||||
} else {
|
|
||||||
bytes := n.Bytes()
|
|
||||||
if len(bytes) > 0 && bytes[0]&0x80 != 0 {
|
|
||||||
// We'll have to pad this with a 0x00 in order to
|
|
||||||
// stop it looking like a negative number.
|
|
||||||
to[0] = 0
|
|
||||||
to = to[1:]
|
|
||||||
length++
|
|
||||||
}
|
|
||||||
nBytes := copy(to, bytes)
|
|
||||||
to = to[nBytes:]
|
|
||||||
length += nBytes
|
|
||||||
}
|
|
||||||
|
|
||||||
lengthBytes[0] = byte(length >> 24)
|
|
||||||
lengthBytes[1] = byte(length >> 16)
|
|
||||||
lengthBytes[2] = byte(length >> 8)
|
|
||||||
lengthBytes[3] = byte(length)
|
|
||||||
return to
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeInt(w io.Writer, n *big.Int) {
|
|
||||||
length := intLength(n)
|
|
||||||
buf := make([]byte, length)
|
|
||||||
marshalInt(buf, n)
|
|
||||||
w.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeString(w io.Writer, s []byte) {
|
|
||||||
var lengthBytes [4]byte
|
|
||||||
lengthBytes[0] = byte(len(s) >> 24)
|
|
||||||
lengthBytes[1] = byte(len(s) >> 16)
|
|
||||||
lengthBytes[2] = byte(len(s) >> 8)
|
|
||||||
lengthBytes[3] = byte(len(s))
|
|
||||||
w.Write(lengthBytes[:])
|
|
||||||
w.Write(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stringLength(n int) int {
|
|
||||||
return 4 + n
|
|
||||||
}
|
|
||||||
|
|
||||||
func marshalString(to []byte, s []byte) []byte {
|
|
||||||
to[0] = byte(len(s) >> 24)
|
|
||||||
to[1] = byte(len(s) >> 16)
|
|
||||||
to[2] = byte(len(s) >> 8)
|
|
||||||
to[3] = byte(len(s))
|
|
||||||
to = to[4:]
|
|
||||||
copy(to, s)
|
|
||||||
return to[len(s):]
|
|
||||||
}
|
|
||||||
|
|
||||||
var bigIntType = reflect.TypeOf((*big.Int)(nil))
|
|
||||||
|
|
||||||
// Decode a packet into its corresponding message.
|
|
||||||
func decode(packet []byte) (interface{}, error) {
|
|
||||||
var msg interface{}
|
|
||||||
switch packet[0] {
|
|
||||||
case msgDisconnect:
|
|
||||||
msg = new(disconnectMsg)
|
|
||||||
case msgServiceRequest:
|
|
||||||
msg = new(serviceRequestMsg)
|
|
||||||
case msgServiceAccept:
|
|
||||||
msg = new(serviceAcceptMsg)
|
|
||||||
case msgKexInit:
|
|
||||||
msg = new(kexInitMsg)
|
|
||||||
case msgKexDHInit:
|
|
||||||
msg = new(kexDHInitMsg)
|
|
||||||
case msgKexDHReply:
|
|
||||||
msg = new(kexDHReplyMsg)
|
|
||||||
case msgUserAuthRequest:
|
|
||||||
msg = new(userAuthRequestMsg)
|
|
||||||
case msgUserAuthFailure:
|
|
||||||
msg = new(userAuthFailureMsg)
|
|
||||||
case msgUserAuthPubKeyOk:
|
|
||||||
msg = new(userAuthPubKeyOkMsg)
|
|
||||||
case msgGlobalRequest:
|
|
||||||
msg = new(globalRequestMsg)
|
|
||||||
case msgRequestSuccess:
|
|
||||||
msg = new(globalRequestSuccessMsg)
|
|
||||||
case msgRequestFailure:
|
|
||||||
msg = new(globalRequestFailureMsg)
|
|
||||||
case msgChannelOpen:
|
|
||||||
msg = new(channelOpenMsg)
|
|
||||||
case msgChannelOpenConfirm:
|
|
||||||
msg = new(channelOpenConfirmMsg)
|
|
||||||
case msgChannelOpenFailure:
|
|
||||||
msg = new(channelOpenFailureMsg)
|
|
||||||
case msgChannelWindowAdjust:
|
|
||||||
msg = new(windowAdjustMsg)
|
|
||||||
case msgChannelEOF:
|
|
||||||
msg = new(channelEOFMsg)
|
|
||||||
case msgChannelClose:
|
|
||||||
msg = new(channelCloseMsg)
|
|
||||||
case msgChannelRequest:
|
|
||||||
msg = new(channelRequestMsg)
|
|
||||||
case msgChannelSuccess:
|
|
||||||
msg = new(channelRequestSuccessMsg)
|
|
||||||
case msgChannelFailure:
|
|
||||||
msg = new(channelRequestFailureMsg)
|
|
||||||
default:
|
|
||||||
return nil, UnexpectedMessageError{0, packet[0]}
|
|
||||||
}
|
|
||||||
if err := unmarshal(msg, packet, packet[0]); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
692
vendor/code.google.com/p/go.crypto/ssh/server.go
generated
vendored
692
vendor/code.google.com/p/go.crypto/ssh/server.go
generated
vendored
@ -1,692 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"crypto/rand"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
_ "crypto/sha1"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ServerConfig struct {
|
|
||||||
hostKeys []Signer
|
|
||||||
|
|
||||||
// Rand provides the source of entropy for key exchange. If Rand is
|
|
||||||
// nil, the cryptographic random reader in package crypto/rand will
|
|
||||||
// be used.
|
|
||||||
Rand io.Reader
|
|
||||||
|
|
||||||
// NoClientAuth is true if clients are allowed to connect without
|
|
||||||
// authenticating.
|
|
||||||
NoClientAuth bool
|
|
||||||
|
|
||||||
// PasswordCallback, if non-nil, is called when a user attempts to
|
|
||||||
// authenticate using a password. It may be called concurrently from
|
|
||||||
// several goroutines.
|
|
||||||
PasswordCallback func(conn *ServerConn, user, password string) bool
|
|
||||||
|
|
||||||
// PublicKeyCallback, if non-nil, is called when a client attempts public
|
|
||||||
// key authentication. It must return true if the given public key is
|
|
||||||
// valid for the given user.
|
|
||||||
PublicKeyCallback func(conn *ServerConn, user, algo string, pubkey []byte) bool
|
|
||||||
|
|
||||||
// KeyboardInteractiveCallback, if non-nil, is called when
|
|
||||||
// keyboard-interactive authentication is selected (RFC
|
|
||||||
// 4256). The client object's Challenge function should be
|
|
||||||
// used to query the user. The callback may offer multiple
|
|
||||||
// Challenge rounds. To avoid information leaks, the client
|
|
||||||
// should be presented a challenge even if the user is
|
|
||||||
// unknown.
|
|
||||||
KeyboardInteractiveCallback func(conn *ServerConn, user string, client ClientKeyboardInteractive) bool
|
|
||||||
|
|
||||||
// Cryptographic-related configuration.
|
|
||||||
Crypto CryptoConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ServerConfig) rand() io.Reader {
|
|
||||||
if c.Rand == nil {
|
|
||||||
return rand.Reader
|
|
||||||
}
|
|
||||||
return c.Rand
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddHostKey adds a private key as a host key. If an existing host
|
|
||||||
// key exists with the same algorithm, it is overwritten.
|
|
||||||
func (s *ServerConfig) AddHostKey(key Signer) {
|
|
||||||
for i, k := range s.hostKeys {
|
|
||||||
if k.PublicKey().PublicKeyAlgo() == key.PublicKey().PublicKeyAlgo() {
|
|
||||||
s.hostKeys[i] = key
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.hostKeys = append(s.hostKeys, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetRSAPrivateKey sets the private key for a Server. A Server must have a
|
|
||||||
// private key configured in order to accept connections. The private key must
|
|
||||||
// be in the form of a PEM encoded, PKCS#1, RSA private key. The file "id_rsa"
|
|
||||||
// typically contains such a key.
|
|
||||||
func (s *ServerConfig) SetRSAPrivateKey(pemBytes []byte) error {
|
|
||||||
priv, err := ParsePrivateKey(pemBytes)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s.AddHostKey(priv)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cachedPubKey contains the results of querying whether a public key is
|
|
||||||
// acceptable for a user. The cache only applies to a single ServerConn.
|
|
||||||
type cachedPubKey struct {
|
|
||||||
user, algo string
|
|
||||||
pubKey []byte
|
|
||||||
result bool
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxCachedPubKeys = 16
|
|
||||||
|
|
||||||
// A ServerConn represents an incoming connection.
|
|
||||||
type ServerConn struct {
|
|
||||||
transport *transport
|
|
||||||
config *ServerConfig
|
|
||||||
|
|
||||||
channels map[uint32]*serverChan
|
|
||||||
nextChanId uint32
|
|
||||||
|
|
||||||
// lock protects err and channels.
|
|
||||||
lock sync.Mutex
|
|
||||||
err error
|
|
||||||
|
|
||||||
// cachedPubKeys contains the cache results of tests for public keys.
|
|
||||||
// Since SSH clients will query whether a public key is acceptable
|
|
||||||
// before attempting to authenticate with it, we end up with duplicate
|
|
||||||
// queries for public key validity.
|
|
||||||
cachedPubKeys []cachedPubKey
|
|
||||||
|
|
||||||
// User holds the successfully authenticated user name.
|
|
||||||
// It is empty if no authentication is used. It is populated before
|
|
||||||
// any authentication callback is called and not assigned to after that.
|
|
||||||
User string
|
|
||||||
|
|
||||||
// ClientVersion is the client's version, populated after
|
|
||||||
// Handshake is called. It should not be modified.
|
|
||||||
ClientVersion []byte
|
|
||||||
|
|
||||||
// Our version.
|
|
||||||
serverVersion []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// Server returns a new SSH server connection
|
|
||||||
// using c as the underlying transport.
|
|
||||||
func Server(c net.Conn, config *ServerConfig) *ServerConn {
|
|
||||||
return &ServerConn{
|
|
||||||
transport: newTransport(c, config.rand(), false /* not client */),
|
|
||||||
channels: make(map[uint32]*serverChan),
|
|
||||||
config: config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// signAndMarshal signs the data with the appropriate algorithm,
|
|
||||||
// and serializes the result in SSH wire format.
|
|
||||||
func signAndMarshal(k Signer, rand io.Reader, data []byte) ([]byte, error) {
|
|
||||||
sig, err := k.Sign(rand, data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serializeSignature(k.PublicKey().PrivateKeyAlgo(), sig), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the connection.
|
|
||||||
func (s *ServerConn) Close() error { return s.transport.Close() }
|
|
||||||
|
|
||||||
// LocalAddr returns the local network address.
|
|
||||||
func (c *ServerConn) LocalAddr() net.Addr { return c.transport.LocalAddr() }
|
|
||||||
|
|
||||||
// RemoteAddr returns the remote network address.
|
|
||||||
func (c *ServerConn) RemoteAddr() net.Addr { return c.transport.RemoteAddr() }
|
|
||||||
|
|
||||||
// Handshake performs an SSH transport and client authentication on the given ServerConn.
|
|
||||||
func (s *ServerConn) Handshake() error {
|
|
||||||
var err error
|
|
||||||
s.serverVersion = []byte(packageVersion)
|
|
||||||
s.ClientVersion, err = exchangeVersions(s.transport.Conn, s.serverVersion)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.clientInitHandshake(nil, nil); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
var packet []byte
|
|
||||||
if packet, err = s.transport.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
var serviceRequest serviceRequestMsg
|
|
||||||
if err := unmarshal(&serviceRequest, packet, msgServiceRequest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if serviceRequest.Service != serviceUserAuth {
|
|
||||||
return errors.New("ssh: requested service '" + serviceRequest.Service + "' before authenticating")
|
|
||||||
}
|
|
||||||
serviceAccept := serviceAcceptMsg{
|
|
||||||
Service: serviceUserAuth,
|
|
||||||
}
|
|
||||||
if err := s.transport.writePacket(marshal(msgServiceAccept, serviceAccept)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := s.authenticate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerConn) clientInitHandshake(clientKexInit *kexInitMsg, clientKexInitPacket []byte) (err error) {
|
|
||||||
serverKexInit := kexInitMsg{
|
|
||||||
KexAlgos: s.config.Crypto.kexes(),
|
|
||||||
CiphersClientServer: s.config.Crypto.ciphers(),
|
|
||||||
CiphersServerClient: s.config.Crypto.ciphers(),
|
|
||||||
MACsClientServer: s.config.Crypto.macs(),
|
|
||||||
MACsServerClient: s.config.Crypto.macs(),
|
|
||||||
CompressionClientServer: supportedCompressions,
|
|
||||||
CompressionServerClient: supportedCompressions,
|
|
||||||
}
|
|
||||||
for _, k := range s.config.hostKeys {
|
|
||||||
serverKexInit.ServerHostKeyAlgos = append(
|
|
||||||
serverKexInit.ServerHostKeyAlgos, k.PublicKey().PublicKeyAlgo())
|
|
||||||
}
|
|
||||||
|
|
||||||
serverKexInitPacket := marshal(msgKexInit, serverKexInit)
|
|
||||||
if err = s.transport.writePacket(serverKexInitPacket); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientKexInitPacket == nil {
|
|
||||||
clientKexInit = new(kexInitMsg)
|
|
||||||
if clientKexInitPacket, err = s.transport.readPacket(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err = unmarshal(clientKexInit, clientKexInitPacket, msgKexInit); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
algs := findAgreedAlgorithms(clientKexInit, &serverKexInit)
|
|
||||||
if algs == nil {
|
|
||||||
return errors.New("ssh: no common algorithms")
|
|
||||||
}
|
|
||||||
|
|
||||||
if clientKexInit.FirstKexFollows && algs.kex != clientKexInit.KexAlgos[0] {
|
|
||||||
// The client sent a Kex message for the wrong algorithm,
|
|
||||||
// which we have to ignore.
|
|
||||||
if _, err = s.transport.readPacket(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var hostKey Signer
|
|
||||||
for _, k := range s.config.hostKeys {
|
|
||||||
if algs.hostKey == k.PublicKey().PublicKeyAlgo() {
|
|
||||||
hostKey = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
kex, ok := kexAlgoMap[algs.kex]
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("ssh: unexpected key exchange algorithm %v", algs.kex)
|
|
||||||
}
|
|
||||||
|
|
||||||
magics := handshakeMagics{
|
|
||||||
serverVersion: s.serverVersion,
|
|
||||||
clientVersion: s.ClientVersion,
|
|
||||||
serverKexInit: marshal(msgKexInit, serverKexInit),
|
|
||||||
clientKexInit: clientKexInitPacket,
|
|
||||||
}
|
|
||||||
result, err := kex.Server(s.transport, s.config.rand(), &magics, hostKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = s.transport.prepareKeyChange(algs, result); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = s.transport.writePacket([]byte{msgNewKeys}); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if packet, err := s.transport.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
} else if packet[0] != msgNewKeys {
|
|
||||||
return UnexpectedMessageError{msgNewKeys, packet[0]}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func isAcceptableAlgo(algo string) bool {
|
|
||||||
switch algo {
|
|
||||||
case KeyAlgoRSA, KeyAlgoDSA, KeyAlgoECDSA256, KeyAlgoECDSA384, KeyAlgoECDSA521,
|
|
||||||
CertAlgoRSAv01, CertAlgoDSAv01, CertAlgoECDSA256v01, CertAlgoECDSA384v01, CertAlgoECDSA521v01:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// testPubKey returns true if the given public key is acceptable for the user.
|
|
||||||
func (s *ServerConn) testPubKey(user, algo string, pubKey []byte) bool {
|
|
||||||
if s.config.PublicKeyCallback == nil || !isAcceptableAlgo(algo) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, c := range s.cachedPubKeys {
|
|
||||||
if c.user == user && c.algo == algo && bytes.Equal(c.pubKey, pubKey) {
|
|
||||||
return c.result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
result := s.config.PublicKeyCallback(s, user, algo, pubKey)
|
|
||||||
if len(s.cachedPubKeys) < maxCachedPubKeys {
|
|
||||||
c := cachedPubKey{
|
|
||||||
user: user,
|
|
||||||
algo: algo,
|
|
||||||
pubKey: make([]byte, len(pubKey)),
|
|
||||||
result: result,
|
|
||||||
}
|
|
||||||
copy(c.pubKey, pubKey)
|
|
||||||
s.cachedPubKeys = append(s.cachedPubKeys, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ServerConn) authenticate() error {
|
|
||||||
var userAuthReq userAuthRequestMsg
|
|
||||||
var err error
|
|
||||||
var packet []byte
|
|
||||||
|
|
||||||
userAuthLoop:
|
|
||||||
for {
|
|
||||||
if packet, err = s.transport.readPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err = unmarshal(&userAuthReq, packet, msgUserAuthRequest); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if userAuthReq.Service != serviceSSH {
|
|
||||||
return errors.New("ssh: client attempted to negotiate for unknown service: " + userAuthReq.Service)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch userAuthReq.Method {
|
|
||||||
case "none":
|
|
||||||
if s.config.NoClientAuth {
|
|
||||||
break userAuthLoop
|
|
||||||
}
|
|
||||||
case "password":
|
|
||||||
if s.config.PasswordCallback == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
payload := userAuthReq.Payload
|
|
||||||
if len(payload) < 1 || payload[0] != 0 {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
payload = payload[1:]
|
|
||||||
password, payload, ok := parseString(payload)
|
|
||||||
if !ok || len(payload) > 0 {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.User = userAuthReq.User
|
|
||||||
if s.config.PasswordCallback(s, userAuthReq.User, string(password)) {
|
|
||||||
break userAuthLoop
|
|
||||||
}
|
|
||||||
case "keyboard-interactive":
|
|
||||||
if s.config.KeyboardInteractiveCallback == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
s.User = userAuthReq.User
|
|
||||||
if s.config.KeyboardInteractiveCallback(s, s.User, &sshClientKeyboardInteractive{s}) {
|
|
||||||
break userAuthLoop
|
|
||||||
}
|
|
||||||
case "publickey":
|
|
||||||
if s.config.PublicKeyCallback == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
payload := userAuthReq.Payload
|
|
||||||
if len(payload) < 1 {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
isQuery := payload[0] == 0
|
|
||||||
payload = payload[1:]
|
|
||||||
algoBytes, payload, ok := parseString(payload)
|
|
||||||
if !ok {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
algo := string(algoBytes)
|
|
||||||
|
|
||||||
pubKey, payload, ok := parseString(payload)
|
|
||||||
if !ok {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
if isQuery {
|
|
||||||
// The client can query if the given public key
|
|
||||||
// would be okay.
|
|
||||||
if len(payload) > 0 {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
if s.testPubKey(userAuthReq.User, algo, pubKey) {
|
|
||||||
okMsg := userAuthPubKeyOkMsg{
|
|
||||||
Algo: algo,
|
|
||||||
PubKey: string(pubKey),
|
|
||||||
}
|
|
||||||
if err = s.transport.writePacket(marshal(msgUserAuthPubKeyOk, okMsg)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
continue userAuthLoop
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
sig, payload, ok := parseSignature(payload)
|
|
||||||
if !ok || len(payload) > 0 {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
// Ensure the public key algo and signature algo
|
|
||||||
// are supported. Compare the private key
|
|
||||||
// algorithm name that corresponds to algo with
|
|
||||||
// sig.Format. This is usually the same, but
|
|
||||||
// for certs, the names differ.
|
|
||||||
if !isAcceptableAlgo(algo) || !isAcceptableAlgo(sig.Format) || pubAlgoToPrivAlgo(algo) != sig.Format {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
signedData := buildDataSignedForAuth(s.transport.sessionID, userAuthReq, algoBytes, pubKey)
|
|
||||||
key, _, ok := ParsePublicKey(pubKey)
|
|
||||||
if !ok {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !key.Verify(signedData, sig.Blob) {
|
|
||||||
return ParseError{msgUserAuthRequest}
|
|
||||||
}
|
|
||||||
// TODO(jmpittman): Implement full validation for certificates.
|
|
||||||
s.User = userAuthReq.User
|
|
||||||
if s.testPubKey(userAuthReq.User, algo, pubKey) {
|
|
||||||
break userAuthLoop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var failureMsg userAuthFailureMsg
|
|
||||||
if s.config.PasswordCallback != nil {
|
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "password")
|
|
||||||
}
|
|
||||||
if s.config.PublicKeyCallback != nil {
|
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "publickey")
|
|
||||||
}
|
|
||||||
if s.config.KeyboardInteractiveCallback != nil {
|
|
||||||
failureMsg.Methods = append(failureMsg.Methods, "keyboard-interactive")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(failureMsg.Methods) == 0 {
|
|
||||||
return errors.New("ssh: no authentication methods configured but NoClientAuth is also false")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = s.transport.writePacket(marshal(msgUserAuthFailure, failureMsg)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
packet = []byte{msgUserAuthSuccess}
|
|
||||||
if err = s.transport.writePacket(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sshClientKeyboardInteractive implements a ClientKeyboardInteractive by
|
|
||||||
// asking the client on the other side of a ServerConn.
|
|
||||||
type sshClientKeyboardInteractive struct {
|
|
||||||
*ServerConn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sshClientKeyboardInteractive) Challenge(user, instruction string, questions []string, echos []bool) (answers []string, err error) {
|
|
||||||
if len(questions) != len(echos) {
|
|
||||||
return nil, errors.New("ssh: echos and questions must have equal length")
|
|
||||||
}
|
|
||||||
|
|
||||||
var prompts []byte
|
|
||||||
for i := range questions {
|
|
||||||
prompts = appendString(prompts, questions[i])
|
|
||||||
prompts = appendBool(prompts, echos[i])
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.transport.writePacket(marshal(msgUserAuthInfoRequest, userAuthInfoRequestMsg{
|
|
||||||
Instruction: instruction,
|
|
||||||
NumPrompts: uint32(len(questions)),
|
|
||||||
Prompts: prompts,
|
|
||||||
})); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
packet, err := c.transport.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if packet[0] != msgUserAuthInfoResponse {
|
|
||||||
return nil, UnexpectedMessageError{msgUserAuthInfoResponse, packet[0]}
|
|
||||||
}
|
|
||||||
packet = packet[1:]
|
|
||||||
|
|
||||||
n, packet, ok := parseUint32(packet)
|
|
||||||
if !ok || int(n) != len(questions) {
|
|
||||||
return nil, &ParseError{msgUserAuthInfoResponse}
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := uint32(0); i < n; i++ {
|
|
||||||
ans, rest, ok := parseString(packet)
|
|
||||||
if !ok {
|
|
||||||
return nil, &ParseError{msgUserAuthInfoResponse}
|
|
||||||
}
|
|
||||||
|
|
||||||
answers = append(answers, string(ans))
|
|
||||||
packet = rest
|
|
||||||
}
|
|
||||||
if len(packet) != 0 {
|
|
||||||
return nil, errors.New("ssh: junk at end of message")
|
|
||||||
}
|
|
||||||
|
|
||||||
return answers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultWindowSize = 32768
|
|
||||||
|
|
||||||
// Accept reads and processes messages on a ServerConn. It must be called
|
|
||||||
// in order to demultiplex messages to any resulting Channels.
|
|
||||||
func (s *ServerConn) Accept() (Channel, error) {
|
|
||||||
// TODO(dfc) s.lock is not held here so visibility of s.err is not guaranteed.
|
|
||||||
if s.err != nil {
|
|
||||||
return nil, s.err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
packet, err := s.transport.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
|
|
||||||
s.lock.Lock()
|
|
||||||
s.err = err
|
|
||||||
s.lock.Unlock()
|
|
||||||
|
|
||||||
// TODO(dfc) s.lock protects s.channels but isn't being held here.
|
|
||||||
for _, c := range s.channels {
|
|
||||||
c.setDead()
|
|
||||||
c.handleData(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch packet[0] {
|
|
||||||
case msgChannelData:
|
|
||||||
if len(packet) < 9 {
|
|
||||||
// malformed data packet
|
|
||||||
return nil, ParseError{msgChannelData}
|
|
||||||
}
|
|
||||||
remoteId := binary.BigEndian.Uint32(packet[1:5])
|
|
||||||
s.lock.Lock()
|
|
||||||
c, ok := s.channels[remoteId]
|
|
||||||
if !ok {
|
|
||||||
s.lock.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if length := binary.BigEndian.Uint32(packet[5:9]); length > 0 {
|
|
||||||
packet = packet[9:]
|
|
||||||
c.handleData(packet[:length])
|
|
||||||
}
|
|
||||||
s.lock.Unlock()
|
|
||||||
default:
|
|
||||||
decoded, err := decode(packet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch msg := decoded.(type) {
|
|
||||||
case *channelOpenMsg:
|
|
||||||
if msg.MaxPacketSize < minPacketLength || msg.MaxPacketSize > 1<<31 {
|
|
||||||
return nil, errors.New("ssh: invalid MaxPacketSize from peer")
|
|
||||||
}
|
|
||||||
c := &serverChan{
|
|
||||||
channel: channel{
|
|
||||||
packetConn: s.transport,
|
|
||||||
remoteId: msg.PeersId,
|
|
||||||
remoteWin: window{Cond: newCond()},
|
|
||||||
maxPacket: msg.MaxPacketSize,
|
|
||||||
},
|
|
||||||
chanType: msg.ChanType,
|
|
||||||
extraData: msg.TypeSpecificData,
|
|
||||||
myWindow: defaultWindowSize,
|
|
||||||
serverConn: s,
|
|
||||||
cond: newCond(),
|
|
||||||
pendingData: make([]byte, defaultWindowSize),
|
|
||||||
}
|
|
||||||
c.remoteWin.add(msg.PeersWindow)
|
|
||||||
s.lock.Lock()
|
|
||||||
c.localId = s.nextChanId
|
|
||||||
s.nextChanId++
|
|
||||||
s.channels[c.localId] = c
|
|
||||||
s.lock.Unlock()
|
|
||||||
return c, nil
|
|
||||||
|
|
||||||
case *channelRequestMsg:
|
|
||||||
s.lock.Lock()
|
|
||||||
c, ok := s.channels[msg.PeersId]
|
|
||||||
if !ok {
|
|
||||||
s.lock.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.handlePacket(msg)
|
|
||||||
s.lock.Unlock()
|
|
||||||
|
|
||||||
case *windowAdjustMsg:
|
|
||||||
s.lock.Lock()
|
|
||||||
c, ok := s.channels[msg.PeersId]
|
|
||||||
if !ok {
|
|
||||||
s.lock.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.handlePacket(msg)
|
|
||||||
s.lock.Unlock()
|
|
||||||
|
|
||||||
case *channelEOFMsg:
|
|
||||||
s.lock.Lock()
|
|
||||||
c, ok := s.channels[msg.PeersId]
|
|
||||||
if !ok {
|
|
||||||
s.lock.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.handlePacket(msg)
|
|
||||||
s.lock.Unlock()
|
|
||||||
|
|
||||||
case *channelCloseMsg:
|
|
||||||
s.lock.Lock()
|
|
||||||
c, ok := s.channels[msg.PeersId]
|
|
||||||
if !ok {
|
|
||||||
s.lock.Unlock()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
c.handlePacket(msg)
|
|
||||||
s.lock.Unlock()
|
|
||||||
|
|
||||||
case *globalRequestMsg:
|
|
||||||
if msg.WantReply {
|
|
||||||
if err := s.transport.writePacket([]byte{msgRequestFailure}); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case *kexInitMsg:
|
|
||||||
s.lock.Lock()
|
|
||||||
if err := s.clientInitHandshake(msg, packet); err != nil {
|
|
||||||
s.lock.Unlock()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
s.lock.Unlock()
|
|
||||||
case *disconnectMsg:
|
|
||||||
return nil, io.EOF
|
|
||||||
default:
|
|
||||||
// Unknown message. Ignore.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// A Listener implements a network listener (net.Listener) for SSH connections.
|
|
||||||
type Listener struct {
|
|
||||||
listener net.Listener
|
|
||||||
config *ServerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns the listener's network address.
|
|
||||||
func (l *Listener) Addr() net.Addr {
|
|
||||||
return l.listener.Addr()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the listener.
|
|
||||||
func (l *Listener) Close() error {
|
|
||||||
return l.listener.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept waits for and returns the next incoming SSH connection.
|
|
||||||
// The receiver should call Handshake() in another goroutine
|
|
||||||
// to avoid blocking the accepter.
|
|
||||||
func (l *Listener) Accept() (*ServerConn, error) {
|
|
||||||
c, err := l.listener.Accept()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Server(c, l.config), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Listen creates an SSH listener accepting connections on
|
|
||||||
// the given network address using net.Listen.
|
|
||||||
func Listen(network, addr string, config *ServerConfig) (*Listener, error) {
|
|
||||||
l, err := net.Listen(network, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &Listener{
|
|
||||||
l,
|
|
||||||
config,
|
|
||||||
}, nil
|
|
||||||
}
|
|
81
vendor/code.google.com/p/go.crypto/ssh/server_terminal.go
generated
vendored
81
vendor/code.google.com/p/go.crypto/ssh/server_terminal.go
generated
vendored
@ -1,81 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
// A Terminal is capable of parsing and generating virtual terminal
|
|
||||||
// data from an SSH client.
|
|
||||||
type Terminal interface {
|
|
||||||
ReadLine() (line string, err error)
|
|
||||||
SetSize(x, y int)
|
|
||||||
Write([]byte) (int, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServerTerminal contains the state for running a terminal that is capable of
|
|
||||||
// reading lines of input.
|
|
||||||
type ServerTerminal struct {
|
|
||||||
Term Terminal
|
|
||||||
Channel Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// parsePtyRequest parses the payload of the pty-req message and extracts the
|
|
||||||
// dimensions of the terminal. See RFC 4254, section 6.2.
|
|
||||||
func parsePtyRequest(s []byte) (width, height int, ok bool) {
|
|
||||||
_, s, ok = parseString(s)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
width32, s, ok := parseUint32(s)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
height32, _, ok := parseUint32(s)
|
|
||||||
width = int(width32)
|
|
||||||
height = int(height32)
|
|
||||||
if width < 1 {
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
if height < 1 {
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *ServerTerminal) Write(buf []byte) (n int, err error) {
|
|
||||||
return ss.Term.Write(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadLine returns a line of input from the terminal.
|
|
||||||
func (ss *ServerTerminal) ReadLine() (line string, err error) {
|
|
||||||
for {
|
|
||||||
if line, err = ss.Term.ReadLine(); err == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
req, ok := err.(ChannelRequest)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ok = false
|
|
||||||
switch req.Request {
|
|
||||||
case "pty-req":
|
|
||||||
var width, height int
|
|
||||||
width, height, ok = parsePtyRequest(req.Payload)
|
|
||||||
ss.Term.SetSize(width, height)
|
|
||||||
case "shell":
|
|
||||||
ok = true
|
|
||||||
if len(req.Payload) > 0 {
|
|
||||||
// We don't accept any commands, only the default shell.
|
|
||||||
ok = false
|
|
||||||
}
|
|
||||||
case "env":
|
|
||||||
ok = true
|
|
||||||
}
|
|
||||||
if req.WantReply {
|
|
||||||
ss.Channel.AckRequest(ok)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
626
vendor/code.google.com/p/go.crypto/ssh/session.go
generated
vendored
626
vendor/code.google.com/p/go.crypto/ssh/session.go
generated
vendored
@ -1,626 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
// Session implements an interactive session described in
|
|
||||||
// "RFC 4254, section 6".
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Signal string
|
|
||||||
|
|
||||||
// POSIX signals as listed in RFC 4254 Section 6.10.
|
|
||||||
const (
|
|
||||||
SIGABRT Signal = "ABRT"
|
|
||||||
SIGALRM Signal = "ALRM"
|
|
||||||
SIGFPE Signal = "FPE"
|
|
||||||
SIGHUP Signal = "HUP"
|
|
||||||
SIGILL Signal = "ILL"
|
|
||||||
SIGINT Signal = "INT"
|
|
||||||
SIGKILL Signal = "KILL"
|
|
||||||
SIGPIPE Signal = "PIPE"
|
|
||||||
SIGQUIT Signal = "QUIT"
|
|
||||||
SIGSEGV Signal = "SEGV"
|
|
||||||
SIGTERM Signal = "TERM"
|
|
||||||
SIGUSR1 Signal = "USR1"
|
|
||||||
SIGUSR2 Signal = "USR2"
|
|
||||||
)
|
|
||||||
|
|
||||||
var signals = map[Signal]int{
|
|
||||||
SIGABRT: 6,
|
|
||||||
SIGALRM: 14,
|
|
||||||
SIGFPE: 8,
|
|
||||||
SIGHUP: 1,
|
|
||||||
SIGILL: 4,
|
|
||||||
SIGINT: 2,
|
|
||||||
SIGKILL: 9,
|
|
||||||
SIGPIPE: 13,
|
|
||||||
SIGQUIT: 3,
|
|
||||||
SIGSEGV: 11,
|
|
||||||
SIGTERM: 15,
|
|
||||||
}
|
|
||||||
|
|
||||||
type TerminalModes map[uint8]uint32
|
|
||||||
|
|
||||||
// POSIX terminal mode flags as listed in RFC 4254 Section 8.
|
|
||||||
const (
|
|
||||||
tty_OP_END = 0
|
|
||||||
VINTR = 1
|
|
||||||
VQUIT = 2
|
|
||||||
VERASE = 3
|
|
||||||
VKILL = 4
|
|
||||||
VEOF = 5
|
|
||||||
VEOL = 6
|
|
||||||
VEOL2 = 7
|
|
||||||
VSTART = 8
|
|
||||||
VSTOP = 9
|
|
||||||
VSUSP = 10
|
|
||||||
VDSUSP = 11
|
|
||||||
VREPRINT = 12
|
|
||||||
VWERASE = 13
|
|
||||||
VLNEXT = 14
|
|
||||||
VFLUSH = 15
|
|
||||||
VSWTCH = 16
|
|
||||||
VSTATUS = 17
|
|
||||||
VDISCARD = 18
|
|
||||||
IGNPAR = 30
|
|
||||||
PARMRK = 31
|
|
||||||
INPCK = 32
|
|
||||||
ISTRIP = 33
|
|
||||||
INLCR = 34
|
|
||||||
IGNCR = 35
|
|
||||||
ICRNL = 36
|
|
||||||
IUCLC = 37
|
|
||||||
IXON = 38
|
|
||||||
IXANY = 39
|
|
||||||
IXOFF = 40
|
|
||||||
IMAXBEL = 41
|
|
||||||
ISIG = 50
|
|
||||||
ICANON = 51
|
|
||||||
XCASE = 52
|
|
||||||
ECHO = 53
|
|
||||||
ECHOE = 54
|
|
||||||
ECHOK = 55
|
|
||||||
ECHONL = 56
|
|
||||||
NOFLSH = 57
|
|
||||||
TOSTOP = 58
|
|
||||||
IEXTEN = 59
|
|
||||||
ECHOCTL = 60
|
|
||||||
ECHOKE = 61
|
|
||||||
PENDIN = 62
|
|
||||||
OPOST = 70
|
|
||||||
OLCUC = 71
|
|
||||||
ONLCR = 72
|
|
||||||
OCRNL = 73
|
|
||||||
ONOCR = 74
|
|
||||||
ONLRET = 75
|
|
||||||
CS7 = 90
|
|
||||||
CS8 = 91
|
|
||||||
PARENB = 92
|
|
||||||
PARODD = 93
|
|
||||||
TTY_OP_ISPEED = 128
|
|
||||||
TTY_OP_OSPEED = 129
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Session represents a connection to a remote command or shell.
|
|
||||||
type Session struct {
|
|
||||||
// Stdin specifies the remote process's standard input.
|
|
||||||
// If Stdin is nil, the remote process reads from an empty
|
|
||||||
// bytes.Buffer.
|
|
||||||
Stdin io.Reader
|
|
||||||
|
|
||||||
// Stdout and Stderr specify the remote process's standard
|
|
||||||
// output and error.
|
|
||||||
//
|
|
||||||
// If either is nil, Run connects the corresponding file
|
|
||||||
// descriptor to an instance of ioutil.Discard. There is a
|
|
||||||
// fixed amount of buffering that is shared for the two streams.
|
|
||||||
// If either blocks it may eventually cause the remote
|
|
||||||
// command to block.
|
|
||||||
Stdout io.Writer
|
|
||||||
Stderr io.Writer
|
|
||||||
|
|
||||||
*clientChan // the channel backing this session
|
|
||||||
|
|
||||||
started bool // true once Start, Run or Shell is invoked.
|
|
||||||
copyFuncs []func() error
|
|
||||||
errors chan error // one send per copyFunc
|
|
||||||
|
|
||||||
// true if pipe method is active
|
|
||||||
stdinpipe, stdoutpipe, stderrpipe bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 4254 Section 6.4.
|
|
||||||
type setenvRequest struct {
|
|
||||||
PeersId uint32
|
|
||||||
Request string
|
|
||||||
WantReply bool
|
|
||||||
Name string
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 4254 Section 6.5.
|
|
||||||
type subsystemRequestMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
Request string
|
|
||||||
WantReply bool
|
|
||||||
Subsystem string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setenv sets an environment variable that will be applied to any
|
|
||||||
// command executed by Shell or Run.
|
|
||||||
func (s *Session) Setenv(name, value string) error {
|
|
||||||
req := setenvRequest{
|
|
||||||
PeersId: s.remoteId,
|
|
||||||
Request: "env",
|
|
||||||
WantReply: true,
|
|
||||||
Name: name,
|
|
||||||
Value: value,
|
|
||||||
}
|
|
||||||
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.waitForResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 4254 Section 6.2.
|
|
||||||
type ptyRequestMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
Request string
|
|
||||||
WantReply bool
|
|
||||||
Term string
|
|
||||||
Columns uint32
|
|
||||||
Rows uint32
|
|
||||||
Width uint32
|
|
||||||
Height uint32
|
|
||||||
Modelist string
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestPty requests the association of a pty with the session on the remote host.
|
|
||||||
func (s *Session) RequestPty(term string, h, w int, termmodes TerminalModes) error {
|
|
||||||
var tm []byte
|
|
||||||
for k, v := range termmodes {
|
|
||||||
tm = append(tm, k)
|
|
||||||
tm = appendU32(tm, v)
|
|
||||||
}
|
|
||||||
tm = append(tm, tty_OP_END)
|
|
||||||
req := ptyRequestMsg{
|
|
||||||
PeersId: s.remoteId,
|
|
||||||
Request: "pty-req",
|
|
||||||
WantReply: true,
|
|
||||||
Term: term,
|
|
||||||
Columns: uint32(w),
|
|
||||||
Rows: uint32(h),
|
|
||||||
Width: uint32(w * 8),
|
|
||||||
Height: uint32(h * 8),
|
|
||||||
Modelist: string(tm),
|
|
||||||
}
|
|
||||||
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.waitForResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RequestSubsystem requests the association of a subsystem with the session on the remote host.
|
|
||||||
// A subsystem is a predefined command that runs in the background when the ssh session is initiated
|
|
||||||
func (s *Session) RequestSubsystem(subsystem string) error {
|
|
||||||
req := subsystemRequestMsg{
|
|
||||||
PeersId: s.remoteId,
|
|
||||||
Request: "subsystem",
|
|
||||||
WantReply: true,
|
|
||||||
Subsystem: subsystem,
|
|
||||||
}
|
|
||||||
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.waitForResponse()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 4254 Section 6.9.
|
|
||||||
type signalMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
Request string
|
|
||||||
WantReply bool
|
|
||||||
Signal string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal sends the given signal to the remote process.
|
|
||||||
// sig is one of the SIG* constants.
|
|
||||||
func (s *Session) Signal(sig Signal) error {
|
|
||||||
req := signalMsg{
|
|
||||||
PeersId: s.remoteId,
|
|
||||||
Request: "signal",
|
|
||||||
WantReply: false,
|
|
||||||
Signal: string(sig),
|
|
||||||
}
|
|
||||||
return s.writePacket(marshal(msgChannelRequest, req))
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 4254 Section 6.5.
|
|
||||||
type execMsg struct {
|
|
||||||
PeersId uint32
|
|
||||||
Request string
|
|
||||||
WantReply bool
|
|
||||||
Command string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start runs cmd on the remote host. Typically, the remote
|
|
||||||
// server passes cmd to the shell for interpretation.
|
|
||||||
// A Session only accepts one call to Run, Start or Shell.
|
|
||||||
func (s *Session) Start(cmd string) error {
|
|
||||||
if s.started {
|
|
||||||
return errors.New("ssh: session already started")
|
|
||||||
}
|
|
||||||
req := execMsg{
|
|
||||||
PeersId: s.remoteId,
|
|
||||||
Request: "exec",
|
|
||||||
WantReply: true,
|
|
||||||
Command: cmd,
|
|
||||||
}
|
|
||||||
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.waitForResponse(); err != nil {
|
|
||||||
return fmt.Errorf("ssh: could not execute command %s: %v", cmd, err)
|
|
||||||
}
|
|
||||||
return s.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run runs cmd on the remote host. Typically, the remote
|
|
||||||
// server passes cmd to the shell for interpretation.
|
|
||||||
// A Session only accepts one call to Run, Start, Shell, Output,
|
|
||||||
// or CombinedOutput.
|
|
||||||
//
|
|
||||||
// The returned error is nil if the command runs, has no problems
|
|
||||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
|
||||||
// status.
|
|
||||||
//
|
|
||||||
// If the command fails to run or doesn't complete successfully, the
|
|
||||||
// error is of type *ExitError. Other error types may be
|
|
||||||
// returned for I/O problems.
|
|
||||||
func (s *Session) Run(cmd string) error {
|
|
||||||
err := s.Start(cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.Wait()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output runs cmd on the remote host and returns its standard output.
|
|
||||||
func (s *Session) Output(cmd string) ([]byte, error) {
|
|
||||||
if s.Stdout != nil {
|
|
||||||
return nil, errors.New("ssh: Stdout already set")
|
|
||||||
}
|
|
||||||
var b bytes.Buffer
|
|
||||||
s.Stdout = &b
|
|
||||||
err := s.Run(cmd)
|
|
||||||
return b.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
type singleWriter struct {
|
|
||||||
b bytes.Buffer
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *singleWriter) Write(p []byte) (int, error) {
|
|
||||||
w.mu.Lock()
|
|
||||||
defer w.mu.Unlock()
|
|
||||||
return w.b.Write(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CombinedOutput runs cmd on the remote host and returns its combined
|
|
||||||
// standard output and standard error.
|
|
||||||
func (s *Session) CombinedOutput(cmd string) ([]byte, error) {
|
|
||||||
if s.Stdout != nil {
|
|
||||||
return nil, errors.New("ssh: Stdout already set")
|
|
||||||
}
|
|
||||||
if s.Stderr != nil {
|
|
||||||
return nil, errors.New("ssh: Stderr already set")
|
|
||||||
}
|
|
||||||
var b singleWriter
|
|
||||||
s.Stdout = &b
|
|
||||||
s.Stderr = &b
|
|
||||||
err := s.Run(cmd)
|
|
||||||
return b.b.Bytes(), err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shell starts a login shell on the remote host. A Session only
|
|
||||||
// accepts one call to Run, Start, Shell, Output, or CombinedOutput.
|
|
||||||
func (s *Session) Shell() error {
|
|
||||||
if s.started {
|
|
||||||
return errors.New("ssh: session already started")
|
|
||||||
}
|
|
||||||
req := channelRequestMsg{
|
|
||||||
PeersId: s.remoteId,
|
|
||||||
Request: "shell",
|
|
||||||
WantReply: true,
|
|
||||||
}
|
|
||||||
if err := s.writePacket(marshal(msgChannelRequest, req)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := s.waitForResponse(); err != nil {
|
|
||||||
return fmt.Errorf("ssh: could not execute shell: %v", err)
|
|
||||||
}
|
|
||||||
return s.start()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) waitForResponse() error {
|
|
||||||
msg := <-s.msg
|
|
||||||
switch msg.(type) {
|
|
||||||
case *channelRequestSuccessMsg:
|
|
||||||
return nil
|
|
||||||
case *channelRequestFailureMsg:
|
|
||||||
return errors.New("ssh: request failed")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("ssh: unknown packet %T received: %v", msg, msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) start() error {
|
|
||||||
s.started = true
|
|
||||||
|
|
||||||
type F func(*Session)
|
|
||||||
for _, setupFd := range []F{(*Session).stdin, (*Session).stdout, (*Session).stderr} {
|
|
||||||
setupFd(s)
|
|
||||||
}
|
|
||||||
|
|
||||||
s.errors = make(chan error, len(s.copyFuncs))
|
|
||||||
for _, fn := range s.copyFuncs {
|
|
||||||
go func(fn func() error) {
|
|
||||||
s.errors <- fn()
|
|
||||||
}(fn)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait waits for the remote command to exit.
|
|
||||||
//
|
|
||||||
// The returned error is nil if the command runs, has no problems
|
|
||||||
// copying stdin, stdout, and stderr, and exits with a zero exit
|
|
||||||
// status.
|
|
||||||
//
|
|
||||||
// If the command fails to run or doesn't complete successfully, the
|
|
||||||
// error is of type *ExitError. Other error types may be
|
|
||||||
// returned for I/O problems.
|
|
||||||
func (s *Session) Wait() error {
|
|
||||||
if !s.started {
|
|
||||||
return errors.New("ssh: session not started")
|
|
||||||
}
|
|
||||||
waitErr := s.wait()
|
|
||||||
|
|
||||||
var copyError error
|
|
||||||
for _ = range s.copyFuncs {
|
|
||||||
if err := <-s.errors; err != nil && copyError == nil {
|
|
||||||
copyError = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if waitErr != nil {
|
|
||||||
return waitErr
|
|
||||||
}
|
|
||||||
return copyError
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) wait() error {
|
|
||||||
wm := Waitmsg{status: -1}
|
|
||||||
|
|
||||||
// Wait for msg channel to be closed before returning.
|
|
||||||
for msg := range s.msg {
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *channelRequestMsg:
|
|
||||||
switch msg.Request {
|
|
||||||
case "exit-status":
|
|
||||||
d := msg.RequestSpecificData
|
|
||||||
wm.status = int(d[0])<<24 | int(d[1])<<16 | int(d[2])<<8 | int(d[3])
|
|
||||||
case "exit-signal":
|
|
||||||
signal, rest, ok := parseString(msg.RequestSpecificData)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
|
|
||||||
}
|
|
||||||
wm.signal = safeString(string(signal))
|
|
||||||
|
|
||||||
// skip coreDumped bool
|
|
||||||
if len(rest) == 0 {
|
|
||||||
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
|
|
||||||
}
|
|
||||||
rest = rest[1:]
|
|
||||||
|
|
||||||
errmsg, rest, ok := parseString(rest)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
|
|
||||||
}
|
|
||||||
wm.msg = safeString(string(errmsg))
|
|
||||||
|
|
||||||
lang, _, ok := parseString(rest)
|
|
||||||
if !ok {
|
|
||||||
return fmt.Errorf("wait: could not parse request data: %v", msg.RequestSpecificData)
|
|
||||||
}
|
|
||||||
wm.lang = safeString(string(lang))
|
|
||||||
default:
|
|
||||||
// This handles keepalives and matches
|
|
||||||
// OpenSSH's behaviour.
|
|
||||||
if msg.WantReply {
|
|
||||||
s.writePacket(marshal(msgChannelFailure, channelRequestFailureMsg{
|
|
||||||
PeersId: s.remoteId,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("wait: unexpected packet %T received: %v", msg, msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if wm.status == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if wm.status == -1 {
|
|
||||||
// exit-status was never sent from server
|
|
||||||
if wm.signal == "" {
|
|
||||||
return errors.New("wait: remote command exited without exit status or exit signal")
|
|
||||||
}
|
|
||||||
wm.status = 128
|
|
||||||
if _, ok := signals[Signal(wm.signal)]; ok {
|
|
||||||
wm.status += signals[Signal(wm.signal)]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return &ExitError{wm}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) stdin() {
|
|
||||||
if s.stdinpipe {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.Stdin == nil {
|
|
||||||
s.Stdin = new(bytes.Buffer)
|
|
||||||
}
|
|
||||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
|
||||||
_, err := io.Copy(s.clientChan.stdin, s.Stdin)
|
|
||||||
if err1 := s.clientChan.stdin.Close(); err == nil && err1 != io.EOF {
|
|
||||||
err = err1
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) stdout() {
|
|
||||||
if s.stdoutpipe {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.Stdout == nil {
|
|
||||||
s.Stdout = ioutil.Discard
|
|
||||||
}
|
|
||||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
|
||||||
_, err := io.Copy(s.Stdout, s.clientChan.stdout)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Session) stderr() {
|
|
||||||
if s.stderrpipe {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if s.Stderr == nil {
|
|
||||||
s.Stderr = ioutil.Discard
|
|
||||||
}
|
|
||||||
s.copyFuncs = append(s.copyFuncs, func() error {
|
|
||||||
_, err := io.Copy(s.Stderr, s.clientChan.stderr)
|
|
||||||
return err
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// StdinPipe returns a pipe that will be connected to the
|
|
||||||
// remote command's standard input when the command starts.
|
|
||||||
func (s *Session) StdinPipe() (io.WriteCloser, error) {
|
|
||||||
if s.Stdin != nil {
|
|
||||||
return nil, errors.New("ssh: Stdin already set")
|
|
||||||
}
|
|
||||||
if s.started {
|
|
||||||
return nil, errors.New("ssh: StdinPipe after process started")
|
|
||||||
}
|
|
||||||
s.stdinpipe = true
|
|
||||||
return s.clientChan.stdin, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StdoutPipe returns a pipe that will be connected to the
|
|
||||||
// remote command's standard output when the command starts.
|
|
||||||
// There is a fixed amount of buffering that is shared between
|
|
||||||
// stdout and stderr streams. If the StdoutPipe reader is
|
|
||||||
// not serviced fast enough it may eventually cause the
|
|
||||||
// remote command to block.
|
|
||||||
func (s *Session) StdoutPipe() (io.Reader, error) {
|
|
||||||
if s.Stdout != nil {
|
|
||||||
return nil, errors.New("ssh: Stdout already set")
|
|
||||||
}
|
|
||||||
if s.started {
|
|
||||||
return nil, errors.New("ssh: StdoutPipe after process started")
|
|
||||||
}
|
|
||||||
s.stdoutpipe = true
|
|
||||||
return s.clientChan.stdout, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// StderrPipe returns a pipe that will be connected to the
|
|
||||||
// remote command's standard error when the command starts.
|
|
||||||
// There is a fixed amount of buffering that is shared between
|
|
||||||
// stdout and stderr streams. If the StderrPipe reader is
|
|
||||||
// not serviced fast enough it may eventually cause the
|
|
||||||
// remote command to block.
|
|
||||||
func (s *Session) StderrPipe() (io.Reader, error) {
|
|
||||||
if s.Stderr != nil {
|
|
||||||
return nil, errors.New("ssh: Stderr already set")
|
|
||||||
}
|
|
||||||
if s.started {
|
|
||||||
return nil, errors.New("ssh: StderrPipe after process started")
|
|
||||||
}
|
|
||||||
s.stderrpipe = true
|
|
||||||
return s.clientChan.stderr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSession returns a new interactive session on the remote host.
|
|
||||||
func (c *ClientConn) NewSession() (*Session, error) {
|
|
||||||
ch := c.newChan(c.transport)
|
|
||||||
if err := c.transport.writePacket(marshal(msgChannelOpen, channelOpenMsg{
|
|
||||||
ChanType: "session",
|
|
||||||
PeersId: ch.localId,
|
|
||||||
PeersWindow: channelWindowSize,
|
|
||||||
MaxPacketSize: channelMaxPacketSize,
|
|
||||||
})); err != nil {
|
|
||||||
c.chanList.remove(ch.localId)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := ch.waitForChannelOpenResponse(); err != nil {
|
|
||||||
c.chanList.remove(ch.localId)
|
|
||||||
return nil, fmt.Errorf("ssh: unable to open session: %v", err)
|
|
||||||
}
|
|
||||||
return &Session{
|
|
||||||
clientChan: ch,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// An ExitError reports unsuccessful completion of a remote command.
|
|
||||||
type ExitError struct {
|
|
||||||
Waitmsg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e *ExitError) Error() string {
|
|
||||||
return e.Waitmsg.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Waitmsg stores the information about an exited remote command
|
|
||||||
// as reported by Wait.
|
|
||||||
type Waitmsg struct {
|
|
||||||
status int
|
|
||||||
signal string
|
|
||||||
msg string
|
|
||||||
lang string
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExitStatus returns the exit status of the remote command.
|
|
||||||
func (w Waitmsg) ExitStatus() int {
|
|
||||||
return w.status
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signal returns the exit signal of the remote command if
|
|
||||||
// it was terminated violently.
|
|
||||||
func (w Waitmsg) Signal() string {
|
|
||||||
return w.signal
|
|
||||||
}
|
|
||||||
|
|
||||||
// Msg returns the exit message given by the remote command
|
|
||||||
func (w Waitmsg) Msg() string {
|
|
||||||
return w.msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lang returns the language tag. See RFC 3066
|
|
||||||
func (w Waitmsg) Lang() string {
|
|
||||||
return w.lang
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w Waitmsg) String() string {
|
|
||||||
return fmt.Sprintf("Process exited with: %v. Reason was: %v (%v)", w.status, w.msg, w.signal)
|
|
||||||
}
|
|
367
vendor/code.google.com/p/go.crypto/ssh/tcpip.go
generated
vendored
367
vendor/code.google.com/p/go.crypto/ssh/tcpip.go
generated
vendored
@ -1,367 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Listen requests the remote peer open a listening socket
|
|
||||||
// on addr. Incoming connections will be available by calling
|
|
||||||
// Accept on the returned net.Listener.
|
|
||||||
func (c *ClientConn) Listen(n, addr string) (net.Listener, error) {
|
|
||||||
laddr, err := net.ResolveTCPAddr(n, addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return c.ListenTCP(laddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Automatic port allocation is broken with OpenSSH before 6.0. See
|
|
||||||
// also https://bugzilla.mindrot.org/show_bug.cgi?id=2017. In
|
|
||||||
// particular, OpenSSH 5.9 sends a channelOpenMsg with port number 0,
|
|
||||||
// rather than the actual port number. This means you can never open
|
|
||||||
// two different listeners with auto allocated ports. We work around
|
|
||||||
// this by trying explicit ports until we succeed.
|
|
||||||
|
|
||||||
const openSSHPrefix = "OpenSSH_"
|
|
||||||
|
|
||||||
var portRandomizer = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
||||||
|
|
||||||
// isBrokenOpenSSHVersion returns true if the given version string
|
|
||||||
// specifies a version of OpenSSH that is known to have a bug in port
|
|
||||||
// forwarding.
|
|
||||||
func isBrokenOpenSSHVersion(versionStr string) bool {
|
|
||||||
i := strings.Index(versionStr, openSSHPrefix)
|
|
||||||
if i < 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
i += len(openSSHPrefix)
|
|
||||||
j := i
|
|
||||||
for ; j < len(versionStr); j++ {
|
|
||||||
if versionStr[j] < '0' || versionStr[j] > '9' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
version, _ := strconv.Atoi(versionStr[i:j])
|
|
||||||
return version < 6
|
|
||||||
}
|
|
||||||
|
|
||||||
// autoPortListenWorkaround simulates automatic port allocation by
|
|
||||||
// trying random ports repeatedly.
|
|
||||||
func (c *ClientConn) autoPortListenWorkaround(laddr *net.TCPAddr) (net.Listener, error) {
|
|
||||||
var sshListener net.Listener
|
|
||||||
var err error
|
|
||||||
const tries = 10
|
|
||||||
for i := 0; i < tries; i++ {
|
|
||||||
addr := *laddr
|
|
||||||
addr.Port = 1024 + portRandomizer.Intn(60000)
|
|
||||||
sshListener, err = c.ListenTCP(&addr)
|
|
||||||
if err == nil {
|
|
||||||
laddr.Port = addr.Port
|
|
||||||
return sshListener, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("ssh: listen on random port failed after %d tries: %v", tries, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 4254 7.1
|
|
||||||
type channelForwardMsg struct {
|
|
||||||
Message string
|
|
||||||
WantReply bool
|
|
||||||
raddr string
|
|
||||||
rport uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListenTCP requests the remote peer open a listening socket
|
|
||||||
// on laddr. Incoming connections will be available by calling
|
|
||||||
// Accept on the returned net.Listener.
|
|
||||||
func (c *ClientConn) ListenTCP(laddr *net.TCPAddr) (net.Listener, error) {
|
|
||||||
if laddr.Port == 0 && isBrokenOpenSSHVersion(c.serverVersion) {
|
|
||||||
return c.autoPortListenWorkaround(laddr)
|
|
||||||
}
|
|
||||||
|
|
||||||
m := channelForwardMsg{
|
|
||||||
"tcpip-forward",
|
|
||||||
true, // sendGlobalRequest waits for a reply
|
|
||||||
laddr.IP.String(),
|
|
||||||
uint32(laddr.Port),
|
|
||||||
}
|
|
||||||
// send message
|
|
||||||
resp, err := c.sendGlobalRequest(m)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the original port was 0, then the remote side will
|
|
||||||
// supply a real port number in the response.
|
|
||||||
if laddr.Port == 0 {
|
|
||||||
port, _, ok := parseUint32(resp.Data)
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("unable to parse response")
|
|
||||||
}
|
|
||||||
laddr.Port = int(port)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register this forward, using the port number we obtained.
|
|
||||||
ch := c.forwardList.add(*laddr)
|
|
||||||
|
|
||||||
return &tcpListener{laddr, c, ch}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// forwardList stores a mapping between remote
|
|
||||||
// forward requests and the tcpListeners.
|
|
||||||
type forwardList struct {
|
|
||||||
sync.Mutex
|
|
||||||
entries []forwardEntry
|
|
||||||
}
|
|
||||||
|
|
||||||
// forwardEntry represents an established mapping of a laddr on a
|
|
||||||
// remote ssh server to a channel connected to a tcpListener.
|
|
||||||
type forwardEntry struct {
|
|
||||||
laddr net.TCPAddr
|
|
||||||
c chan forward
|
|
||||||
}
|
|
||||||
|
|
||||||
// forward represents an incoming forwarded tcpip connection. The
|
|
||||||
// arguments to add/remove/lookup should be address as specified in
|
|
||||||
// the original forward-request.
|
|
||||||
type forward struct {
|
|
||||||
c *clientChan // the ssh client channel underlying this forward
|
|
||||||
raddr *net.TCPAddr // the raddr of the incoming connection
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *forwardList) add(addr net.TCPAddr) chan forward {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
f := forwardEntry{
|
|
||||||
addr,
|
|
||||||
make(chan forward, 1),
|
|
||||||
}
|
|
||||||
l.entries = append(l.entries, f)
|
|
||||||
return f.c
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove removes the forward entry, and the channel feeding its
|
|
||||||
// listener.
|
|
||||||
func (l *forwardList) remove(addr net.TCPAddr) {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
for i, f := range l.entries {
|
|
||||||
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
|
|
||||||
l.entries = append(l.entries[:i], l.entries[i+1:]...)
|
|
||||||
close(f.c)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// closeAll closes and clears all forwards.
|
|
||||||
func (l *forwardList) closeAll() {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
for _, f := range l.entries {
|
|
||||||
close(f.c)
|
|
||||||
}
|
|
||||||
l.entries = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *forwardList) lookup(addr net.TCPAddr) (chan forward, bool) {
|
|
||||||
l.Lock()
|
|
||||||
defer l.Unlock()
|
|
||||||
for _, f := range l.entries {
|
|
||||||
if addr.IP.Equal(f.laddr.IP) && addr.Port == f.laddr.Port {
|
|
||||||
return f.c, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpListener struct {
|
|
||||||
laddr *net.TCPAddr
|
|
||||||
|
|
||||||
conn *ClientConn
|
|
||||||
in <-chan forward
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accept waits for and returns the next connection to the listener.
|
|
||||||
func (l *tcpListener) Accept() (net.Conn, error) {
|
|
||||||
s, ok := <-l.in
|
|
||||||
if !ok {
|
|
||||||
return nil, io.EOF
|
|
||||||
}
|
|
||||||
return &tcpChanConn{
|
|
||||||
tcpChan: &tcpChan{
|
|
||||||
clientChan: s.c,
|
|
||||||
Reader: s.c.stdout,
|
|
||||||
Writer: s.c.stdin,
|
|
||||||
},
|
|
||||||
laddr: l.laddr,
|
|
||||||
raddr: s.raddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the listener.
|
|
||||||
func (l *tcpListener) Close() error {
|
|
||||||
m := channelForwardMsg{
|
|
||||||
"cancel-tcpip-forward",
|
|
||||||
true,
|
|
||||||
l.laddr.IP.String(),
|
|
||||||
uint32(l.laddr.Port),
|
|
||||||
}
|
|
||||||
l.conn.forwardList.remove(*l.laddr)
|
|
||||||
if _, err := l.conn.sendGlobalRequest(m); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Addr returns the listener's network address.
|
|
||||||
func (l *tcpListener) Addr() net.Addr {
|
|
||||||
return l.laddr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dial initiates a connection to the addr from the remote host.
|
|
||||||
// The resulting connection has a zero LocalAddr() and RemoteAddr().
|
|
||||||
func (c *ClientConn) Dial(n, addr string) (net.Conn, error) {
|
|
||||||
// Parse the address into host and numeric port.
|
|
||||||
host, portString, err := net.SplitHostPort(addr)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
port, err := strconv.ParseUint(portString, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Use a zero address for local and remote address.
|
|
||||||
zeroAddr := &net.TCPAddr{
|
|
||||||
IP: net.IPv4zero,
|
|
||||||
Port: 0,
|
|
||||||
}
|
|
||||||
ch, err := c.dial(net.IPv4zero.String(), 0, host, int(port))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &tcpChanConn{
|
|
||||||
tcpChan: ch,
|
|
||||||
laddr: zeroAddr,
|
|
||||||
raddr: zeroAddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DialTCP connects to the remote address raddr on the network net,
|
|
||||||
// which must be "tcp", "tcp4", or "tcp6". If laddr is not nil, it is used
|
|
||||||
// as the local address for the connection.
|
|
||||||
func (c *ClientConn) DialTCP(n string, laddr, raddr *net.TCPAddr) (net.Conn, error) {
|
|
||||||
if laddr == nil {
|
|
||||||
laddr = &net.TCPAddr{
|
|
||||||
IP: net.IPv4zero,
|
|
||||||
Port: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ch, err := c.dial(laddr.IP.String(), laddr.Port, raddr.IP.String(), raddr.Port)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &tcpChanConn{
|
|
||||||
tcpChan: ch,
|
|
||||||
laddr: laddr,
|
|
||||||
raddr: raddr,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC 4254 7.2
|
|
||||||
type channelOpenDirectMsg struct {
|
|
||||||
ChanType string
|
|
||||||
PeersId uint32
|
|
||||||
PeersWindow uint32
|
|
||||||
MaxPacketSize uint32
|
|
||||||
raddr string
|
|
||||||
rport uint32
|
|
||||||
laddr string
|
|
||||||
lport uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// dial opens a direct-tcpip connection to the remote server. laddr and raddr are passed as
|
|
||||||
// strings and are expected to be resolvable at the remote end.
|
|
||||||
func (c *ClientConn) dial(laddr string, lport int, raddr string, rport int) (*tcpChan, error) {
|
|
||||||
ch := c.newChan(c.transport)
|
|
||||||
if err := c.transport.writePacket(marshal(msgChannelOpen, channelOpenDirectMsg{
|
|
||||||
ChanType: "direct-tcpip",
|
|
||||||
PeersId: ch.localId,
|
|
||||||
PeersWindow: channelWindowSize,
|
|
||||||
MaxPacketSize: channelMaxPacketSize,
|
|
||||||
raddr: raddr,
|
|
||||||
rport: uint32(rport),
|
|
||||||
laddr: laddr,
|
|
||||||
lport: uint32(lport),
|
|
||||||
})); err != nil {
|
|
||||||
c.chanList.remove(ch.localId)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if err := ch.waitForChannelOpenResponse(); err != nil {
|
|
||||||
c.chanList.remove(ch.localId)
|
|
||||||
return nil, fmt.Errorf("ssh: unable to open direct tcpip connection: %v", err)
|
|
||||||
}
|
|
||||||
return &tcpChan{
|
|
||||||
clientChan: ch,
|
|
||||||
Reader: ch.stdout,
|
|
||||||
Writer: ch.stdin,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type tcpChan struct {
|
|
||||||
*clientChan // the backing channel
|
|
||||||
io.Reader
|
|
||||||
io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// tcpChanConn fulfills the net.Conn interface without
|
|
||||||
// the tcpChan having to hold laddr or raddr directly.
|
|
||||||
type tcpChanConn struct {
|
|
||||||
*tcpChan
|
|
||||||
laddr, raddr net.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
// LocalAddr returns the local network address.
|
|
||||||
func (t *tcpChanConn) LocalAddr() net.Addr {
|
|
||||||
return t.laddr
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoteAddr returns the remote network address.
|
|
||||||
func (t *tcpChanConn) RemoteAddr() net.Addr {
|
|
||||||
return t.raddr
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetDeadline sets the read and write deadlines associated
|
|
||||||
// with the connection.
|
|
||||||
func (t *tcpChanConn) SetDeadline(deadline time.Time) error {
|
|
||||||
if err := t.SetReadDeadline(deadline); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return t.SetWriteDeadline(deadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetReadDeadline sets the read deadline.
|
|
||||||
// A zero value for t means Read will not time out.
|
|
||||||
// After the deadline, the error from Read will implement net.Error
|
|
||||||
// with Timeout() == true.
|
|
||||||
func (t *tcpChanConn) SetReadDeadline(deadline time.Time) error {
|
|
||||||
return errors.New("ssh: tcpChan: deadline not supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetWriteDeadline exists to satisfy the net.Conn interface
|
|
||||||
// but is not implemented by this type. It always returns an error.
|
|
||||||
func (t *tcpChanConn) SetWriteDeadline(deadline time.Time) error {
|
|
||||||
return errors.New("ssh: tcpChan: deadline not supported")
|
|
||||||
}
|
|
426
vendor/code.google.com/p/go.crypto/ssh/transport.go
generated
vendored
426
vendor/code.google.com/p/go.crypto/ssh/transport.go
generated
vendored
@ -1,426 +0,0 @@
|
|||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ssh
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"crypto/cipher"
|
|
||||||
"crypto/subtle"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"hash"
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
packetSizeMultiple = 16 // TODO(huin) this should be determined by the cipher.
|
|
||||||
|
|
||||||
// RFC 4253 section 6.1 defines a minimum packet size of 32768 that implementations
|
|
||||||
// MUST be able to process (plus a few more kilobytes for padding and mac). The RFC
|
|
||||||
// indicates implementations SHOULD be able to handle larger packet sizes, but then
|
|
||||||
// waffles on about reasonable limits.
|
|
||||||
//
|
|
||||||
// OpenSSH caps their maxPacket at 256kb so we choose to do the same.
|
|
||||||
maxPacket = 256 * 1024
|
|
||||||
)
|
|
||||||
|
|
||||||
// packetConn represents a transport that implements packet based
|
|
||||||
// operations.
|
|
||||||
type packetConn interface {
|
|
||||||
// Encrypt and send a packet of data to the remote peer.
|
|
||||||
writePacket(packet []byte) error
|
|
||||||
|
|
||||||
// Read a packet from the connection
|
|
||||||
readPacket() ([]byte, error)
|
|
||||||
|
|
||||||
// Close closes the write-side of the connection.
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
// transport represents the SSH connection to the remote peer.
|
|
||||||
type transport struct {
|
|
||||||
reader
|
|
||||||
writer
|
|
||||||
|
|
||||||
net.Conn
|
|
||||||
|
|
||||||
// Initial H used for the session ID. Once assigned this does
|
|
||||||
// not change, even during subsequent key exchanges.
|
|
||||||
sessionID []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// reader represents the incoming connection state.
|
|
||||||
type reader struct {
|
|
||||||
io.Reader
|
|
||||||
common
|
|
||||||
}
|
|
||||||
|
|
||||||
// writer represents the outgoing connection state.
|
|
||||||
type writer struct {
|
|
||||||
sync.Mutex // protects writer.Writer from concurrent writes
|
|
||||||
*bufio.Writer
|
|
||||||
rand io.Reader
|
|
||||||
common
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareKeyChange sets up key material for a keychange. The key changes in
|
|
||||||
// both directions are triggered by reading and writing a msgNewKey packet
|
|
||||||
// respectively.
|
|
||||||
func (t *transport) prepareKeyChange(algs *algorithms, kexResult *kexResult) error {
|
|
||||||
t.writer.cipherAlgo = algs.wCipher
|
|
||||||
t.writer.macAlgo = algs.wMAC
|
|
||||||
t.writer.compressionAlgo = algs.wCompression
|
|
||||||
|
|
||||||
t.reader.cipherAlgo = algs.rCipher
|
|
||||||
t.reader.macAlgo = algs.rMAC
|
|
||||||
t.reader.compressionAlgo = algs.rCompression
|
|
||||||
|
|
||||||
if t.sessionID == nil {
|
|
||||||
t.sessionID = kexResult.H
|
|
||||||
}
|
|
||||||
|
|
||||||
kexResult.SessionID = t.sessionID
|
|
||||||
t.reader.pendingKeyChange <- kexResult
|
|
||||||
t.writer.pendingKeyChange <- kexResult
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// common represents the cipher state needed to process messages in a single
|
|
||||||
// direction.
|
|
||||||
type common struct {
|
|
||||||
seqNum uint32
|
|
||||||
mac hash.Hash
|
|
||||||
cipher cipher.Stream
|
|
||||||
|
|
||||||
cipherAlgo string
|
|
||||||
macAlgo string
|
|
||||||
compressionAlgo string
|
|
||||||
|
|
||||||
dir direction
|
|
||||||
pendingKeyChange chan *kexResult
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read and decrypt a single packet from the remote peer.
|
|
||||||
func (r *reader) readPacket() ([]byte, error) {
|
|
||||||
var lengthBytes = make([]byte, 5)
|
|
||||||
var macSize uint32
|
|
||||||
if _, err := io.ReadFull(r, lengthBytes); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
r.cipher.XORKeyStream(lengthBytes, lengthBytes)
|
|
||||||
|
|
||||||
if r.mac != nil {
|
|
||||||
r.mac.Reset()
|
|
||||||
seqNumBytes := []byte{
|
|
||||||
byte(r.seqNum >> 24),
|
|
||||||
byte(r.seqNum >> 16),
|
|
||||||
byte(r.seqNum >> 8),
|
|
||||||
byte(r.seqNum),
|
|
||||||
}
|
|
||||||
r.mac.Write(seqNumBytes)
|
|
||||||
r.mac.Write(lengthBytes)
|
|
||||||
macSize = uint32(r.mac.Size())
|
|
||||||
}
|
|
||||||
|
|
||||||
length := binary.BigEndian.Uint32(lengthBytes[0:4])
|
|
||||||
paddingLength := uint32(lengthBytes[4])
|
|
||||||
|
|
||||||
if length <= paddingLength+1 {
|
|
||||||
return nil, errors.New("ssh: invalid packet length, packet too small")
|
|
||||||
}
|
|
||||||
|
|
||||||
if length > maxPacket {
|
|
||||||
return nil, errors.New("ssh: invalid packet length, packet too large")
|
|
||||||
}
|
|
||||||
|
|
||||||
packet := make([]byte, length-1+macSize)
|
|
||||||
if _, err := io.ReadFull(r, packet); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
mac := packet[length-1:]
|
|
||||||
r.cipher.XORKeyStream(packet, packet[:length-1])
|
|
||||||
|
|
||||||
if r.mac != nil {
|
|
||||||
r.mac.Write(packet[:length-1])
|
|
||||||
if subtle.ConstantTimeCompare(r.mac.Sum(nil), mac) != 1 {
|
|
||||||
return nil, errors.New("ssh: MAC failure")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.seqNum++
|
|
||||||
packet = packet[:length-paddingLength-1]
|
|
||||||
|
|
||||||
if len(packet) > 0 && packet[0] == msgNewKeys {
|
|
||||||
select {
|
|
||||||
case k := <-r.pendingKeyChange:
|
|
||||||
if err := r.setupKeys(r.dir, k); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("ssh: got bogus newkeys message.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return packet, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read and decrypt next packet discarding debug and noop messages.
|
|
||||||
func (t *transport) readPacket() ([]byte, error) {
|
|
||||||
for {
|
|
||||||
packet, err := t.reader.readPacket()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(packet) == 0 {
|
|
||||||
return nil, errors.New("ssh: zero length packet")
|
|
||||||
}
|
|
||||||
|
|
||||||
if packet[0] != msgIgnore && packet[0] != msgDebug {
|
|
||||||
return packet, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt and send a packet of data to the remote peer.
|
|
||||||
func (w *writer) writePacket(packet []byte) error {
|
|
||||||
changeKeys := len(packet) > 0 && packet[0] == msgNewKeys
|
|
||||||
|
|
||||||
if len(packet) > maxPacket {
|
|
||||||
return errors.New("ssh: packet too large")
|
|
||||||
}
|
|
||||||
w.Mutex.Lock()
|
|
||||||
defer w.Mutex.Unlock()
|
|
||||||
|
|
||||||
paddingLength := packetSizeMultiple - (5+len(packet))%packetSizeMultiple
|
|
||||||
if paddingLength < 4 {
|
|
||||||
paddingLength += packetSizeMultiple
|
|
||||||
}
|
|
||||||
|
|
||||||
length := len(packet) + 1 + paddingLength
|
|
||||||
lengthBytes := []byte{
|
|
||||||
byte(length >> 24),
|
|
||||||
byte(length >> 16),
|
|
||||||
byte(length >> 8),
|
|
||||||
byte(length),
|
|
||||||
byte(paddingLength),
|
|
||||||
}
|
|
||||||
padding := make([]byte, paddingLength)
|
|
||||||
_, err := io.ReadFull(w.rand, padding)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.mac != nil {
|
|
||||||
w.mac.Reset()
|
|
||||||
seqNumBytes := []byte{
|
|
||||||
byte(w.seqNum >> 24),
|
|
||||||
byte(w.seqNum >> 16),
|
|
||||||
byte(w.seqNum >> 8),
|
|
||||||
byte(w.seqNum),
|
|
||||||
}
|
|
||||||
w.mac.Write(seqNumBytes)
|
|
||||||
w.mac.Write(lengthBytes)
|
|
||||||
w.mac.Write(packet)
|
|
||||||
w.mac.Write(padding)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(dfc) lengthBytes, packet and padding should be
|
|
||||||
// subslices of a single buffer
|
|
||||||
w.cipher.XORKeyStream(lengthBytes, lengthBytes)
|
|
||||||
w.cipher.XORKeyStream(packet, packet)
|
|
||||||
w.cipher.XORKeyStream(padding, padding)
|
|
||||||
|
|
||||||
if _, err := w.Write(lengthBytes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(packet); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if _, err := w.Write(padding); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if w.mac != nil {
|
|
||||||
if _, err := w.Write(w.mac.Sum(nil)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.seqNum++
|
|
||||||
if err = w.Flush(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if changeKeys {
|
|
||||||
select {
|
|
||||||
case k := <-w.pendingKeyChange:
|
|
||||||
err = w.setupKeys(w.dir, k)
|
|
||||||
default:
|
|
||||||
panic("ssh: no key material for msgNewKeys")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func newTransport(conn net.Conn, rand io.Reader, isClient bool) *transport {
|
|
||||||
t := &transport{
|
|
||||||
reader: reader{
|
|
||||||
Reader: bufio.NewReader(conn),
|
|
||||||
common: common{
|
|
||||||
cipher: noneCipher{},
|
|
||||||
pendingKeyChange: make(chan *kexResult, 1),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
writer: writer{
|
|
||||||
Writer: bufio.NewWriter(conn),
|
|
||||||
rand: rand,
|
|
||||||
common: common{
|
|
||||||
cipher: noneCipher{},
|
|
||||||
pendingKeyChange: make(chan *kexResult, 1),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Conn: conn,
|
|
||||||
}
|
|
||||||
if isClient {
|
|
||||||
t.reader.dir = serverKeys
|
|
||||||
t.writer.dir = clientKeys
|
|
||||||
} else {
|
|
||||||
t.reader.dir = clientKeys
|
|
||||||
t.writer.dir = serverKeys
|
|
||||||
}
|
|
||||||
|
|
||||||
return t
|
|
||||||
}
|
|
||||||
|
|
||||||
type direction struct {
|
|
||||||
ivTag []byte
|
|
||||||
keyTag []byte
|
|
||||||
macKeyTag []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(dfc) can this be made a constant ?
|
|
||||||
var (
|
|
||||||
serverKeys = direction{[]byte{'B'}, []byte{'D'}, []byte{'F'}}
|
|
||||||
clientKeys = direction{[]byte{'A'}, []byte{'C'}, []byte{'E'}}
|
|
||||||
)
|
|
||||||
|
|
||||||
// setupKeys sets the cipher and MAC keys from kex.K, kex.H and sessionId, as
|
|
||||||
// described in RFC 4253, section 6.4. direction should either be serverKeys
|
|
||||||
// (to setup server->client keys) or clientKeys (for client->server keys).
|
|
||||||
func (c *common) setupKeys(d direction, r *kexResult) error {
|
|
||||||
cipherMode := cipherModes[c.cipherAlgo]
|
|
||||||
macMode := macModes[c.macAlgo]
|
|
||||||
|
|
||||||
iv := make([]byte, cipherMode.ivSize)
|
|
||||||
key := make([]byte, cipherMode.keySize)
|
|
||||||
macKey := make([]byte, macMode.keySize)
|
|
||||||
|
|
||||||
h := r.Hash.New()
|
|
||||||
generateKeyMaterial(iv, d.ivTag, r.K, r.H, r.SessionID, h)
|
|
||||||
generateKeyMaterial(key, d.keyTag, r.K, r.H, r.SessionID, h)
|
|
||||||
generateKeyMaterial(macKey, d.macKeyTag, r.K, r.H, r.SessionID, h)
|
|
||||||
|
|
||||||
c.mac = macMode.new(macKey)
|
|
||||||
|
|
||||||
var err error
|
|
||||||
c.cipher, err = cipherMode.createCipher(key, iv)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// generateKeyMaterial fills out with key material generated from tag, K, H
|
|
||||||
// and sessionId, as specified in RFC 4253, section 7.2.
|
|
||||||
func generateKeyMaterial(out, tag []byte, K, H, sessionId []byte, h hash.Hash) {
|
|
||||||
var digestsSoFar []byte
|
|
||||||
|
|
||||||
for len(out) > 0 {
|
|
||||||
h.Reset()
|
|
||||||
h.Write(K)
|
|
||||||
h.Write(H)
|
|
||||||
|
|
||||||
if len(digestsSoFar) == 0 {
|
|
||||||
h.Write(tag)
|
|
||||||
h.Write(sessionId)
|
|
||||||
} else {
|
|
||||||
h.Write(digestsSoFar)
|
|
||||||
}
|
|
||||||
|
|
||||||
digest := h.Sum(nil)
|
|
||||||
n := copy(out, digest)
|
|
||||||
out = out[n:]
|
|
||||||
if len(out) > 0 {
|
|
||||||
digestsSoFar = append(digestsSoFar, digest...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageVersion = "SSH-2.0-Go"
|
|
||||||
|
|
||||||
// Sends and receives a version line. The versionLine string should
|
|
||||||
// be US ASCII, start with "SSH-2.0-", and should not include a
|
|
||||||
// newline. exchangeVersions returns the other side's version line.
|
|
||||||
func exchangeVersions(rw io.ReadWriter, versionLine []byte) (them []byte, err error) {
|
|
||||||
// Contrary to the RFC, we do not ignore lines that don't
|
|
||||||
// start with "SSH-2.0-" to make the library usable with
|
|
||||||
// nonconforming servers.
|
|
||||||
for _, c := range versionLine {
|
|
||||||
// The spec disallows non US-ASCII chars, and
|
|
||||||
// specifically forbids null chars.
|
|
||||||
if c < 32 {
|
|
||||||
return nil, errors.New("ssh: junk character in version line")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if _, err = rw.Write(append(versionLine, '\r', '\n')); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
them, err = readVersion(rw)
|
|
||||||
return them, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// maxVersionStringBytes is the maximum number of bytes that we'll
|
|
||||||
// accept as a version string. RFC 4253 section 4.2 limits this at 255
|
|
||||||
// chars
|
|
||||||
const maxVersionStringBytes = 255
|
|
||||||
|
|
||||||
// Read version string as specified by RFC 4253, section 4.2.
|
|
||||||
func readVersion(r io.Reader) ([]byte, error) {
|
|
||||||
versionString := make([]byte, 0, 64)
|
|
||||||
var ok bool
|
|
||||||
var buf [1]byte
|
|
||||||
|
|
||||||
for len(versionString) < maxVersionStringBytes {
|
|
||||||
_, err := io.ReadFull(r, buf[:])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// The RFC says that the version should be terminated with \r\n
|
|
||||||
// but several SSH servers actually only send a \n.
|
|
||||||
if buf[0] == '\n' {
|
|
||||||
ok = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
// non ASCII chars are disallowed, but we are lenient,
|
|
||||||
// since Go doesn't use null-terminated strings.
|
|
||||||
|
|
||||||
// The RFC allows a comment after a space, however,
|
|
||||||
// all of it (version and comments) goes into the
|
|
||||||
// session hash.
|
|
||||||
versionString = append(versionString, buf[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("ssh: overflow reading version string")
|
|
||||||
}
|
|
||||||
|
|
||||||
// There might be a '\r' on the end which we should remove.
|
|
||||||
if len(versionString) > 0 && versionString[len(versionString)-1] == '\r' {
|
|
||||||
versionString = versionString[:len(versionString)-1]
|
|
||||||
}
|
|
||||||
return versionString, nil
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user