mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-11-26 20:01:02 +00:00
Merge pull request #1967 from bradrydzewski/master
Promote new agent <> communication
This commit is contained in:
commit
b05b54a2ef
228 changed files with 6304 additions and 23951 deletions
22
.drone.yml
22
.drone.yml
|
@ -22,17 +22,17 @@ pipeline:
|
|||
when:
|
||||
event: push
|
||||
|
||||
|
||||
# archive:
|
||||
# image: plugins/s3
|
||||
# acl: public-read
|
||||
# bucket: downloads.drone.io
|
||||
# source: release/**/*.*
|
||||
# access_key: ${AWS_ACCESS_KEY_ID}
|
||||
# secret_key: ${AWS_SECRET_ACCESS_KEY}
|
||||
# when:
|
||||
# event: push
|
||||
# branch: master
|
||||
archive:
|
||||
image: plugins/s3
|
||||
acl: public-read
|
||||
bucket: downloads.drone.io
|
||||
source: release/**/*.*
|
||||
target: /0.6.0/
|
||||
access_key: ${AWS_ACCESS_KEY_ID}
|
||||
secret_key: ${AWS_SECRET_ACCESS_KEY}
|
||||
when:
|
||||
event: push
|
||||
branch: master
|
||||
|
||||
publish:
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"net/url"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/mq/logger"
|
||||
"github.com/drone/mq/stomp"
|
||||
"github.com/tidwall/redlog"
|
||||
"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/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/samalba/dockerclient"
|
||||
"github.com/tevino/abool"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// AgentCmd is the exported command for starting the drone agent.
|
||||
var AgentCmd = cli.Command{
|
||||
Name: "agent",
|
||||
Usage: "starts the drone agent",
|
||||
Action: start,
|
||||
Action: loop,
|
||||
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{
|
||||
EnvVar: "DRONE_SERVER,DRONE_ENDPOINT",
|
||||
Name: "drone-server",
|
||||
Usage: "drone server address",
|
||||
Value: "ws://localhost:8000/ws/broker",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_TOKEN",
|
||||
Name: "drone-token",
|
||||
Usage: "drone authorization token",
|
||||
},
|
||||
cli.StringFlag{
|
||||
EnvVar: "DRONE_SECRET,DRONE_AGENT_SECRET",
|
||||
Name: "drone-secret",
|
||||
|
@ -83,89 +43,21 @@ var AgentCmd = cli.Command{
|
|||
Usage: "drone server backoff interval",
|
||||
Value: time.Second * 15,
|
||||
},
|
||||
cli.DurationFlag{
|
||||
EnvVar: "DRONE_PING",
|
||||
Name: "ping",
|
||||
Usage: "drone server ping frequency",
|
||||
Value: time.Minute * 5,
|
||||
cli.IntFlag{
|
||||
Name: "retry-limit",
|
||||
EnvVar: "DRONE_RETRY_LIMIT",
|
||||
Value: math.MaxInt32,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_DEBUG",
|
||||
Name: "debug",
|
||||
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{
|
||||
EnvVar: "DRONE_FILTER",
|
||||
Name: "filter",
|
||||
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{
|
||||
Name: "max-procs",
|
||||
EnvVar: "DRONE_MAX_PROCS",
|
||||
|
@ -179,139 +71,188 @@ var AgentCmd = cli.Command{
|
|||
},
|
||||
}
|
||||
|
||||
func start(c *cli.Context) {
|
||||
|
||||
if c.Bool("canary") {
|
||||
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)
|
||||
func loop(c *cli.Context) error {
|
||||
endpoint, err := url.Parse(
|
||||
c.String("drone-server"),
|
||||
)
|
||||
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) {
|
||||
running.Add(1)
|
||||
defer func() {
|
||||
running.Done()
|
||||
client.Ack(m.Ack)
|
||||
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
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
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()
|
||||
|
||||
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.")
|
||||
}
|
||||
wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
// tracks running builds
|
||||
var running sync.WaitGroup
|
||||
const (
|
||||
maxFileUpload = 5000000
|
||||
maxLogsUpload = 5000000
|
||||
)
|
||||
|
||||
func handleSignals() {
|
||||
// Graceful shut-down on SIGINT/SIGTERM
|
||||
c := make(chan os.Signal, 1)
|
||||
signal.Notify(c, os.Interrupt)
|
||||
signal.Notify(c, syscall.SIGTERM)
|
||||
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() {
|
||||
<-c
|
||||
logger.Warningf("SIGTERM received.")
|
||||
logger.Warningf("wait for running builds to finish.")
|
||||
running.Wait()
|
||||
logger.Warningf("done.")
|
||||
os.Exit(0)
|
||||
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,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
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var buildCmd = cli.Command{
|
||||
Name: "build",
|
||||
|
|
|
@ -1,22 +1,17 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildInfoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show build details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildInfo(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "info",
|
||||
Usage: "show build details",
|
||||
Action: buildInfo,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildLastCmd = cli.Command{
|
||||
Name: "last",
|
||||
Usage: "show latest build details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildLast(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "last",
|
||||
Usage: "show latest build details",
|
||||
Action: buildLast,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildListCmd = cli.Command{
|
||||
Name: "list",
|
||||
Usage: "show build history",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "list",
|
||||
Usage: "show build history",
|
||||
Action: buildList,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/build"
|
||||
"github.com/cncd/pipeline/pipeline/rpc"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildLogsCmd = cli.Command{
|
||||
|
@ -61,7 +61,7 @@ func buildLogs(c *cli.Context) error {
|
|||
|
||||
dec := json.NewDecoder(r)
|
||||
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()
|
||||
if err != nil {
|
||||
|
|
|
@ -2,21 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildQueueCmd = cli.Command{
|
||||
Name: "queue",
|
||||
Usage: "show build queue",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildQueue(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "queue",
|
||||
Usage: "show build queue",
|
||||
Action: buildQueue,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -2,21 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildStartCmd = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "start a build",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildStart(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "start",
|
||||
Usage: "start a build",
|
||||
Action: buildStart,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "fork",
|
||||
|
|
|
@ -2,20 +2,15 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var buildStopCmd = cli.Command{
|
||||
Name: "stop",
|
||||
Usage: "stop a build",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := buildStop(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "stop",
|
||||
Usage: "stop a build",
|
||||
Action: buildStop,
|
||||
}
|
||||
|
||||
func buildStop(c *cli.Context) (err error) {
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
package main
|
|
@ -3,22 +3,17 @@ package main
|
|||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var deployCmd = cli.Command{
|
||||
Name: "deploy",
|
||||
Usage: "deploy code",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := deploy(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "deploy",
|
||||
Usage: "deploy code",
|
||||
Action: deploy,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
724
drone/exec.go
724
drone/exec.go
|
@ -1,22 +1,26 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"context"
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/drone/drone/agent"
|
||||
"github.com/drone/drone/build/docker"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/cncd/pipeline/pipeline"
|
||||
"github.com/cncd/pipeline/pipeline/backend"
|
||||
"github.com/cncd/pipeline/pipeline/backend/docker"
|
||||
"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/joho/godotenv"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var execCmd = cli.Command{
|
||||
|
@ -33,439 +37,387 @@ var execCmd = cli.Command{
|
|||
Usage: "build from local directory",
|
||||
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{
|
||||
Name: "timeout",
|
||||
Usage: "build timeout",
|
||||
Value: time.Hour,
|
||||
EnvVar: "DRONE_TIMEOUT",
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout.inactivity",
|
||||
Usage: "build timeout for inactivity",
|
||||
Value: time.Minute * 15,
|
||||
EnvVar: "DRONE_TIMEOUT_INACTIVITY",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_PLUGIN_PULL",
|
||||
Name: "pull",
|
||||
Usage: "always pull latest plugin images",
|
||||
cli.StringSliceFlag{
|
||||
Name: "volumes",
|
||||
Usage: "build volumes",
|
||||
EnvVar: "DRONE_VOLUMES",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
EnvVar: "DRONE_PLUGIN_PRIVILEGED",
|
||||
Name: "privileged",
|
||||
Usage: "plugins that require privileged mode",
|
||||
Name: "privileged",
|
||||
Usage: "privileged plugins",
|
||||
Value: &cli.StringSlice{
|
||||
"plugins/docker",
|
||||
"plugins/docker:*",
|
||||
"plugins/gcr",
|
||||
"plugins/gcr:*",
|
||||
"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
|
||||
// should be kept synchronized.
|
||||
// https://github.com/drone/drone-plugin-starter
|
||||
// Please note the below flags are mirrored in the pipec and
|
||||
// should be kept synchronized. Do not edit directly
|
||||
// https://github.com/cncd/pipeline/pipec
|
||||
//
|
||||
|
||||
//
|
||||
// workspace default
|
||||
//
|
||||
cli.StringFlag{
|
||||
Name: "repo.fullname",
|
||||
Usage: "repository full name",
|
||||
EnvVar: "DRONE_REPO",
|
||||
Name: "workspace-base",
|
||||
Value: "/pipeline",
|
||||
EnvVar: "DRONE_WORKSPACE_BASE",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "repo.owner",
|
||||
Usage: "repository owner",
|
||||
EnvVar: "DRONE_REPO_OWNER",
|
||||
Name: "workspace-path",
|
||||
Value: "src",
|
||||
EnvVar: "DRONE_WORKSPACE_PATH",
|
||||
},
|
||||
//
|
||||
// netrc parameters
|
||||
//
|
||||
cli.StringFlag{
|
||||
Name: "repo.name",
|
||||
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",
|
||||
Name: "netrc-username",
|
||||
EnvVar: "DRONE_NETRC_USERNAME",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "netrc.password",
|
||||
Usage: "previous build sha",
|
||||
Name: "netrc-password",
|
||||
EnvVar: "DRONE_NETRC_PASSWORD",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "netrc.machine",
|
||||
Usage: "previous build sha",
|
||||
Name: "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 {
|
||||
sigterm := make(chan os.Signal, 1)
|
||||
cancelc := make(chan bool, 1)
|
||||
signal.Notify(sigterm, os.Interrupt)
|
||||
go func() {
|
||||
<-sigterm
|
||||
cancelc <- true
|
||||
}()
|
||||
|
||||
path := c.Args().First()
|
||||
if path == "" {
|
||||
path = ".drone.yml"
|
||||
file := c.Args().First()
|
||||
if file == "" {
|
||||
file = ".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 {
|
||||
return err
|
||||
}
|
||||
|
||||
engine, err := docker.New(
|
||||
c.String("docker-host"),
|
||||
c.String("docker-cert-path"),
|
||||
c.Bool("docker-tls-verify"),
|
||||
)
|
||||
conf, err := yaml.ParseString(confstr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
a := agent.Agent{
|
||||
Update: agent.NoopUpdateFunc,
|
||||
Logger: agent.TermLoggerFunc,
|
||||
Engine: engine,
|
||||
Timeout: c.Duration("timeout.inactivity"),
|
||||
Platform: "linux/amd64",
|
||||
Escalate: c.StringSlice("privileged"),
|
||||
Netrc: []string{},
|
||||
Local: dir,
|
||||
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),
|
||||
// configure volumes for local execution
|
||||
volumes := c.StringSlice("volumes")
|
||||
if c.Bool("local") {
|
||||
var (
|
||||
workspaceBase = conf.Workspace.Base
|
||||
workspacePath = conf.Workspace.Path
|
||||
)
|
||||
if workspaceBase == "" {
|
||||
workspaceBase = c.String("workspace-base")
|
||||
}
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(axes) == 0 {
|
||||
axes = append(axes, yaml.Axis{})
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), c.Duration("timeout"))
|
||||
defer cancel()
|
||||
ctx = interrupt.WithContext(ctx)
|
||||
|
||||
var jobs []*model.Job
|
||||
count := 0
|
||||
for _, axis := range axes {
|
||||
jobs = append(jobs, &model.Job{
|
||||
Number: count,
|
||||
Environment: axis,
|
||||
})
|
||||
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
|
||||
return pipeline.New(compiled,
|
||||
pipeline.WithContext(ctx),
|
||||
pipeline.WithLogger(defaultLogger),
|
||||
pipeline.WithTracer(pipeline.DefaultTracer),
|
||||
pipeline.WithLogger(defaultLogger),
|
||||
pipeline.WithEngine(engine),
|
||||
).Run()
|
||||
}
|
||||
|
||||
// helper function to retrieve matrix variables.
|
||||
func getMatrix(c *cli.Context) map[string]string {
|
||||
envs := map[string]string{}
|
||||
for _, s := range c.StringSlice("matrix") {
|
||||
parts := strings.SplitN(s, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
k := parts[0]
|
||||
v := parts[1]
|
||||
envs[k] = v
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// helper function to retrieve secret variables.
|
||||
func getSecrets(c *cli.Context) []*model.Secret {
|
||||
|
||||
var secrets []*model.Secret
|
||||
|
||||
if c.String("secrets-file") != "" {
|
||||
envs, _ := godotenv.Read(c.String("secrets-file"))
|
||||
for k, v := range envs {
|
||||
secret := &model.Secret{
|
||||
Name: k,
|
||||
Value: v,
|
||||
Events: []string{
|
||||
model.EventPull,
|
||||
model.EventPush,
|
||||
model.EventTag,
|
||||
model.EventDeploy,
|
||||
// return the metadata from the cli context.
|
||||
func metadataFromContext(c *cli.Context) frontend.Metadata {
|
||||
return frontend.Metadata{
|
||||
Repo: frontend.Repo{
|
||||
Name: c.String("repo-name"),
|
||||
Link: c.String("repo-link"),
|
||||
Remote: c.String("repo-remote-url"),
|
||||
Private: c.Bool("repo-private"),
|
||||
},
|
||||
Curr: frontend.Build{
|
||||
Number: c.Int("build-number"),
|
||||
Created: c.Int64("build-created"),
|
||||
Started: c.Int64("build-started"),
|
||||
Finished: c.Int64("build-finished"),
|
||||
Status: c.String("build-status"),
|
||||
Event: c.String("build-event"),
|
||||
Link: c.String("build-link"),
|
||||
Target: c.String("build-target"),
|
||||
Commit: frontend.Commit{
|
||||
Sha: c.String("commit-sha"),
|
||||
Ref: c.String("commit-ref"),
|
||||
Refspec: c.String("commit-refspec"),
|
||||
Branch: c.String("commit-branch"),
|
||||
Message: c.String("commit-message"),
|
||||
Author: frontend.Author{
|
||||
Name: c.String("commit-author-name"),
|
||||
Email: c.String("commit-author-email"),
|
||||
Avatar: c.String("commit-author-avatar"),
|
||||
},
|
||||
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{"*"},
|
||||
}
|
||||
secrets = append(secrets, secret)
|
||||
},
|
||||
Prev: frontend.Build{
|
||||
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
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalCmd = cli.Command{
|
||||
Name: "global",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretCmd = cli.Command{
|
||||
Name: "secret",
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a secret",
|
||||
ArgsUsage: "[key] [value]",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := globalSecretAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Flags: secretAddFlags(),
|
||||
Action: globalSecretAdd,
|
||||
Flags: secretAddFlags(),
|
||||
}
|
||||
|
||||
func globalSecretAdd(c *cli.Context) error {
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := globalSecretList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Flags: secretListFlags(),
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: globalSecretList,
|
||||
Flags: secretListFlags(),
|
||||
}
|
||||
|
||||
func globalSecretList(c *cli.Context) error {
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var globalSecretRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a secret",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := globalSecretRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "rm",
|
||||
Usage: "remove a secret",
|
||||
Action: globalSecretRemove,
|
||||
}
|
||||
|
||||
func globalSecretRemove(c *cli.Context) error {
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var infoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show information about the current user",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := info(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "info",
|
||||
Usage: "show information about the current user",
|
||||
Action: info,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/drone/drone/drone/agent"
|
||||
"github.com/drone/drone/version"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/ianschenck/envflag"
|
||||
_ "github.com/joho/godotenv/autoload"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -32,7 +33,6 @@ func main() {
|
|||
}
|
||||
app.Commands = []cli.Command{
|
||||
agent.AgentCmd,
|
||||
agentsCmd,
|
||||
buildCmd,
|
||||
deployCmd,
|
||||
execCmd,
|
||||
|
@ -46,5 +46,8 @@ func main() {
|
|||
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
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgCmd = cli.Command{
|
||||
Name: "org",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgSecretCmd = cli.Command{
|
||||
Name: "secret",
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgSecretAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a secret",
|
||||
ArgsUsage: "[org] [key] [value]",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := orgSecretAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Flags: secretAddFlags(),
|
||||
Action: orgSecretAdd,
|
||||
Flags: secretAddFlags(),
|
||||
}
|
||||
|
||||
func orgSecretAdd(c *cli.Context) error {
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var orgSecretListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := orgSecretList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Flags: secretListFlags(),
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: orgSecretList,
|
||||
Flags: secretListFlags(),
|
||||
}
|
||||
|
||||
func orgSecretList(c *cli.Context) error {
|
||||
|
|
|
@ -3,7 +3,7 @@ package main
|
|||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var orgSecretRemoveCmd = cli.Command{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var repoCmd = cli.Command{
|
||||
Name: "repo",
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "add a repository",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "add",
|
||||
Usage: "add a repository",
|
||||
Action: repoAdd,
|
||||
}
|
||||
|
||||
func repoAdd(c *cli.Context) error {
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoChownCmd = cli.Command{
|
||||
Name: "chown",
|
||||
Usage: "assume ownership of a repository",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoChown(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "chown",
|
||||
Usage: "assume ownership of a repository",
|
||||
Action: repoChown,
|
||||
}
|
||||
|
||||
func repoChown(c *cli.Context) error {
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoInfoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show repository details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoInfo(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "info",
|
||||
Usage: "show repository details",
|
||||
Action: repoInfo,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all repos",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "ls",
|
||||
Usage: "list all repos",
|
||||
Action: repoList,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var repoRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a repository",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := repoRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "rm",
|
||||
Usage: "remove a repository",
|
||||
Action: repoRemove,
|
||||
}
|
||||
|
||||
func repoRemove(c *cli.Context) error {
|
||||
|
|
|
@ -6,8 +6,8 @@ import (
|
|||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var secretCmd = cli.Command{
|
||||
|
|
|
@ -1,21 +1,13 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var secretAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a secret",
|
||||
ArgsUsage: "[repo] [key] [value]",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := secretAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Flags: secretAddFlags(),
|
||||
Action: secretAdd,
|
||||
Flags: secretAddFlags(),
|
||||
}
|
||||
|
||||
func secretAdd(c *cli.Context) error {
|
||||
|
|
|
@ -1,20 +1,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var secretListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := secretList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Flags: secretListFlags(),
|
||||
Name: "ls",
|
||||
Usage: "list all secrets",
|
||||
Action: secretList,
|
||||
Flags: secretListFlags(),
|
||||
}
|
||||
|
||||
func secretList(c *cli.Context) error {
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var secretRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a secret",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := secretRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "rm",
|
||||
Usage: "remove a secret",
|
||||
Action: secretRemove,
|
||||
}
|
||||
|
||||
func secretRemove(c *cli.Context) error {
|
||||
|
|
|
@ -8,18 +8,14 @@ import (
|
|||
"github.com/drone/drone/router/middleware"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gin-gonic/contrib/ginrus"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var serverCmd = cli.Command{
|
||||
Name: "server",
|
||||
Usage: "starts the drone server daemon",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := server(c); err != nil {
|
||||
logrus.Fatal(err)
|
||||
}
|
||||
},
|
||||
Name: "server",
|
||||
Usage: "starts the drone server daemon",
|
||||
Action: server,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
EnvVar: "DRONE_DEBUG",
|
||||
|
@ -301,8 +297,6 @@ func server(c *cli.Context) error {
|
|||
middleware.Cache(c),
|
||||
middleware.Store(c),
|
||||
middleware.Remote(c),
|
||||
middleware.Agents(c),
|
||||
middleware.Broker(c),
|
||||
)
|
||||
|
||||
// start the server with tls enabled
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var signCmd = cli.Command{
|
||||
Name: "sign",
|
||||
Usage: "creates a secure yaml file",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := sign(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "sign",
|
||||
Usage: "creates a secure yaml file",
|
||||
Action: sign,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "in",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package main
|
||||
|
||||
import "github.com/codegangsta/cli"
|
||||
import "github.com/urfave/cli"
|
||||
|
||||
var userCmd = cli.Command{
|
||||
Name: "user",
|
||||
|
|
|
@ -2,20 +2,15 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userAddCmd = cli.Command{
|
||||
Name: "add",
|
||||
Usage: "adds a user",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userAdd(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "add",
|
||||
Usage: "adds a user",
|
||||
Action: userAdd,
|
||||
}
|
||||
|
||||
func userAdd(c *cli.Context) error {
|
||||
|
|
|
@ -2,21 +2,16 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userInfoCmd = cli.Command{
|
||||
Name: "info",
|
||||
Usage: "show user details",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userInfo(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "info",
|
||||
Usage: "show user details",
|
||||
Action: userInfo,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -1,21 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"text/template"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userListCmd = cli.Command{
|
||||
Name: "ls",
|
||||
Usage: "list all users",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userList(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "ls",
|
||||
Usage: "list all users",
|
||||
Action: userList,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "format",
|
||||
|
|
|
@ -2,19 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var userRemoveCmd = cli.Command{
|
||||
Name: "rm",
|
||||
Usage: "remove a user",
|
||||
Action: func(c *cli.Context) {
|
||||
if err := userRemove(c); err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
},
|
||||
Name: "rm",
|
||||
Usage: "remove a user",
|
||||
Action: userRemove,
|
||||
}
|
||||
|
||||
func userRemove(c *cli.Context) error {
|
||||
|
|
|
@ -9,8 +9,8 @@ import (
|
|||
|
||||
"github.com/drone/drone/client"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/jackspirou/syscerts"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
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 (
|
||||
"github.com/drone/drone/cache"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Cache is a middleware function that initializes the Cache and attaches to
|
||||
|
|
|
@ -3,8 +3,8 @@ package middleware
|
|||
import (
|
||||
"github.com/drone/drone/model"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
const configKey = "config"
|
||||
|
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/remote/bitbucket"
|
||||
"github.com/drone/drone/remote/bitbucketserver"
|
||||
|
@ -12,6 +11,7 @@ import (
|
|||
"github.com/drone/drone/remote/gitlab"
|
||||
"github.com/drone/drone/remote/gogs"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
// Remote is a middleware function that initializes the Remote and attaches to
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/codegangsta/cli"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/drone/store/datastore"
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
|
|
@ -2,7 +2,6 @@ package router
|
|||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
|
@ -41,8 +40,6 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
e.GET("/logout", server.GetLogout)
|
||||
e.NoRoute(server.ShowIndex)
|
||||
|
||||
// TODO above will Go away with React UI
|
||||
|
||||
user := e.Group("/api/user")
|
||||
{
|
||||
user.Use(session.MustUser())
|
||||
|
@ -121,46 +118,28 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
badges.GET("/cc.xml", server.GetCC)
|
||||
}
|
||||
|
||||
if os.Getenv("DRONE_CANARY") == "" {
|
||||
e.POST("/hook", server.PostHook)
|
||||
e.POST("/api/hook", server.PostHook)
|
||||
} else {
|
||||
e.POST("/hook", server.PostHook2)
|
||||
e.POST("/api/hook", server.PostHook2)
|
||||
e.POST("/hook", server.PostHook)
|
||||
e.POST("/api/hook", server.PostHook)
|
||||
|
||||
ws := e.Group("/ws")
|
||||
{
|
||||
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") == "" {
|
||||
ws := e.Group("/ws")
|
||||
{
|
||||
ws.GET("/broker", server.Broker)
|
||||
ws.GET("/feed", server.EventStream)
|
||||
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,
|
||||
)
|
||||
}
|
||||
info := e.Group("/api/info")
|
||||
{
|
||||
info.GET("/queue",
|
||||
session.MustAdmin(),
|
||||
server.GetQueueInfo,
|
||||
)
|
||||
}
|
||||
|
||||
auth := e.Group("/authorize")
|
||||
|
@ -191,12 +170,5 @@ func Load(middleware ...gin.HandlerFunc) http.Handler {
|
|||
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
|
||||
}
|
||||
|
|
227
server/build.go
227
server/build.go
|
@ -7,7 +7,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -18,13 +17,10 @@ import (
|
|||
"github.com/drone/drone/remote"
|
||||
"github.com/drone/drone/shared/httputil"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/drone/yaml"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/square/go-jose"
|
||||
|
||||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
"github.com/drone/mq/stomp"
|
||||
)
|
||||
|
||||
func GetBuilds(c *gin.Context) {
|
||||
|
@ -156,229 +152,10 @@ func DeleteBuild(c *gin.Context) {
|
|||
job.ExitCode = 137
|
||||
store.UpdateBuildJob(c, build, job)
|
||||
|
||||
if os.Getenv("DRONE_CANARY") == "" {
|
||||
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)
|
||||
}
|
||||
config.queue.Error(context.Background(), fmt.Sprint(job.ID), queue.ErrCancel)
|
||||
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) {
|
||||
out, err := store.GetBuildQueue(c)
|
||||
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)
|
||||
repo := session.Repo(c)
|
||||
|
|
436
server/hook.go
436
server/hook.go
|
@ -1,28 +1,268 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/square/go-jose"
|
||||
|
||||
log "github.com/Sirupsen/logrus"
|
||||
"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/drone/yaml"
|
||||
"github.com/drone/mq/stomp"
|
||||
"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),
|
||||
)
|
||||
}
|
||||
|
||||
// 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) {
|
||||
remote_ := remote.FromContext(c)
|
||||
|
||||
tmprepo, build, err := remote_.Hook(c.Request)
|
||||
if err != nil {
|
||||
log.Errorf("failure to parse hook. %s", err)
|
||||
logrus.Errorf("failure to parse hook. %s", err)
|
||||
c.AbortWithError(400, err)
|
||||
return
|
||||
}
|
||||
|
@ -31,7 +271,7 @@ func PostHook(c *gin.Context) {
|
|||
return
|
||||
}
|
||||
if tmprepo == nil {
|
||||
log.Errorf("failure to ascertain repo from hook.")
|
||||
logrus.Errorf("failure to ascertain repo from hook.")
|
||||
c.Writer.WriteHeader(400)
|
||||
return
|
||||
}
|
||||
|
@ -40,14 +280,14 @@ func PostHook(c *gin.Context) {
|
|||
// wrapped in square brackets appear in the commit message
|
||||
skipMatch := skipRe.FindString(build.Message)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
repo, err := store.GetRepoOwnerName(c, tmprepo.Owner, tmprepo.Name)
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
@ -57,18 +297,18 @@ func PostHook(c *gin.Context) {
|
|||
return repo.Hash, 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)
|
||||
return
|
||||
}
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
@ -81,33 +321,18 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
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)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := store.GetUser(c, repo.UserID)
|
||||
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)
|
||||
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
|
||||
// may be stale. Therefore, we should refresh prior to dispatching
|
||||
// the job.
|
||||
|
@ -119,26 +344,16 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
// fetch the build file from the database
|
||||
config := ToConfig(c)
|
||||
raw, err := remote_.File(user, repo, build, config.Yaml)
|
||||
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)
|
||||
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, config.Shasum)
|
||||
sec, err := remote_.File(user, repo, build, cfg.Shasum)
|
||||
if err != nil {
|
||||
log.Debugf("cannot find build secrets 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{})
|
||||
logrus.Debugf("cannot find yaml signature for %s. %s", repo.FullName, err)
|
||||
}
|
||||
|
||||
netrc, err := remote_.Netrc(user, repo)
|
||||
|
@ -148,24 +363,28 @@ func PostHook(c *gin.Context) {
|
|||
}
|
||||
|
||||
// verify the branches can be built vs skipped
|
||||
branches := yaml.ParseBranch(raw)
|
||||
if !branches.Match(build.Branch) && build.Event != model.EventTag && build.Event != model.EventDeploy {
|
||||
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 {
|
||||
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 {
|
||||
log.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
logrus.Debugf("cannot parse .drone.yml.sig file. empty file")
|
||||
} else {
|
||||
build.Signed = true
|
||||
output, verr := signature.Verify([]byte(repo.Hash))
|
||||
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) {
|
||||
log.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||
logrus.Debugf("cannot verify .drone.yml.sig file. no match")
|
||||
} else {
|
||||
build.Verified = true
|
||||
}
|
||||
|
@ -175,71 +394,94 @@ func PostHook(c *gin.Context) {
|
|||
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 {
|
||||
log.Errorf("failure to save commit for %s. %s", repo.FullName, err)
|
||||
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)
|
||||
|
||||
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
|
||||
// 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)
|
||||
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,
|
||||
Repo: *repo,
|
||||
Build: *build,
|
||||
},
|
||||
stomp.WithHeader("repo", repo.FullName),
|
||||
stomp.WithHeader("private", strconv.FormatBool(repo.IsPrivate)),
|
||||
)
|
||||
})
|
||||
// TODO remove global reference
|
||||
config.pubsub.Publish(c, "topic/events", message)
|
||||
//
|
||||
// end publish topic
|
||||
//
|
||||
|
||||
for _, job := range jobs {
|
||||
broker, _ := stomp.FromContext(c)
|
||||
broker.SendJSON("/queue/pending", &model.Work{
|
||||
Signed: build.Signed,
|
||||
Verified: build.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),
|
||||
),
|
||||
)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -12,7 +13,6 @@ import (
|
|||
"github.com/drone/drone/model"
|
||||
"github.com/drone/drone/router/middleware/session"
|
||||
"github.com/drone/drone/store"
|
||||
"github.com/drone/mq/stomp"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
@ -28,160 +28,17 @@ var (
|
|||
|
||||
// Send pings to client with this period. Must be less than pongWait.
|
||||
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) {
|
||||
defer ws.Close()
|
||||
ws.SetReadLimit(512)
|
||||
|
@ -198,14 +55,7 @@ func reader(ws *websocket.Conn) {
|
|||
}
|
||||
}
|
||||
|
||||
//
|
||||
// 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) {
|
||||
func LogStream(c *gin.Context) {
|
||||
repo := session.Repo(c)
|
||||
buildn, _ := strconv.Atoi(c.Param("build"))
|
||||
jobn, _ := strconv.Atoi(c.Param("number"))
|
||||
|
@ -286,7 +136,7 @@ func LogStream2(c *gin.Context) {
|
|||
reader(ws)
|
||||
}
|
||||
|
||||
func EventStream2(c *gin.Context) {
|
||||
func EventStream(c *gin.Context) {
|
||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
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 a new issue