Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Support rotate root manifests #967

Merged
merged 11 commits into from
Dec 9, 2020
115 changes: 109 additions & 6 deletions cmd/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,19 @@
package cmd

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"path/filepath"
"runtime"
"sort"
"strings"
"time"

cjson "github.com/gibson042/canonicaljson-go"
"github.com/pingcap/errors"
"github.com/pingcap/tiup/pkg/cluster/spec"
"github.com/pingcap/tiup/pkg/environment"
Expand All @@ -33,6 +37,7 @@ import (
"github.com/pingcap/tiup/pkg/repository/v1manifest"
"github.com/pingcap/tiup/pkg/set"
"github.com/pingcap/tiup/pkg/utils"
"github.com/pingcap/tiup/server/rotate"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
Expand Down Expand Up @@ -64,29 +69,62 @@ of components or the repository itself.`,
newMirrorSetCmd(),
newMirrorModifyCmd(),
newMirrorGrantCmd(),
newMirrorRotateCmd(),
)

return cmd
}

// the `mirror sign` sub command
func newMirrorSignCmd() *cobra.Command {
privPath := ""
timeout := 10

cmd := &cobra.Command{
Use: "sign <manifest-file> [key-files]",
Use: "sign <manifest-file>",
Short: "Add signatures to a manifest file",
Long: "Add signatures to a manifest file, if no key file specified, the ~/.tiup/keys/private.json will be used",
Long: fmt.Sprintf("Add signatures to a manifest file, if no key file specified, the ~/.tiup/keys/%s will be used", localdata.DefaultPrivateKeyName),
RunE: func(cmd *cobra.Command, args []string) error {
env := environment.GlobalEnv()
if len(args) < 1 {
return cmd.Help()
}

if len(args) == 1 {
return v1manifest.SignManifestFile(args[0], env.Profile().Path(localdata.KeyInfoParentDir, "private.json"))
if privPath == "" {
privPath = env.Profile().Path(localdata.KeyInfoParentDir, localdata.DefaultPrivateKeyName)
}

if !strings.HasPrefix(args[0], "http") {
return v1manifest.SignManifestFile(args[0], args[1:]...)
}

client := utils.NewHTTPClient(time.Duration(timeout)*time.Second, nil)
data, err := client.Get(args[0])
if err != nil {
return err
}

m := &v1manifest.Manifest{Signed: &v1manifest.Root{}}
if err := cjson.Unmarshal(data, m); err != nil {
return errors.Annotate(err, "unmarshal response")
}
return v1manifest.SignManifestFile(args[0], args[1:]...)
m, err = sign(privPath, m.Signed)
if err != nil {
return err
}
data, err = cjson.Marshal(m)
if err != nil {
return errors.Annotate(err, "marshal manifest")
}

if _, err = client.Post(args[0], bytes.NewBuffer(data)); err != nil {
return err
}
return nil
},
}
cmd.Flags().StringVarP(&privPath, "key", "k", "", "Specify the private key path")
cmd.Flags().IntVarP(&timeout, "timeout", "", timeout, "Specify the timeout when access the network")

return cmd
}
Expand Down Expand Up @@ -237,10 +275,75 @@ func newMirrorModifyCmd() *cobra.Command {
return cmd
}

// the `mirror rotate` sub command
func newMirrorRotateCmd() *cobra.Command {
addr := "0.0.0.0:8080"

cmd := &cobra.Command{
Use: "rotate",
Short: "Rotate root.json",
Long: "Rotate root.json make it possible to modify root.json",
RunE: func(cmd *cobra.Command, args []string) error {
root, err := editLatestRootManifest()
if err != nil {
return err
}

manifest, err := rotate.Serve(addr, root)
if err != nil {
return err
}

return environment.GlobalEnv().V1Repository().Mirror().Rotate(manifest)
},
}
cmd.Flags().StringVarP(&addr, "addr", "", addr, "listen address:port when starting the temp server for rotating")

return cmd
}

func editLatestRootManifest() (*v1manifest.Root, error) {
root, err := environment.GlobalEnv().V1Repository().FetchRootManfiest()
if err != nil {
return nil, err
}

file, err := ioutil.TempFile(os.TempDir(), "*.root.json")
if err != nil {
return nil, errors.Annotate(err, "create temp file for root.json")
}
defer file.Close()
name := file.Name()

encoder := json.NewEncoder(file)
encoder.SetIndent("", " ")
if err := encoder.Encode(root); err != nil {
return nil, errors.Annotate(err, "encode root.json")
}
if err := file.Close(); err != nil {
return nil, errors.Annotatef(err, "close %s", name)
}
if err := utils.OpenFileInEditor(name); err != nil {
return nil, err
}

root = &v1manifest.Root{}
file, err = os.Open(name)
if err != nil {
return nil, errors.Annotatef(err, "open %s", name)
}
defer file.Close()
if err := json.NewDecoder(file).Decode(root); err != nil {
return nil, errors.Annotatef(err, "decode %s", name)
}

return root, nil
}

func loadPrivKey(privPath string) (*v1manifest.KeyInfo, error) {
env := environment.GlobalEnv()
if privPath == "" {
privPath = env.Profile().Path(localdata.KeyInfoParentDir, "private.json")
privPath = env.Profile().Path(localdata.KeyInfoParentDir, localdata.DefaultPrivateKeyName)
}

// Get the private key
Expand Down
3 changes: 3 additions & 0 deletions pkg/localdata/constant.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ const (
// KeyInfoParentDir represent the parent directory of all keys
KeyInfoParentDir = "keys"

// DefaultPrivateKeyName represents the default private key file stored in ${TIUP_HOME}/keys
DefaultPrivateKeyName = "private.json"

// DataParentDir represent the parent directory of all running instances
DataParentDir = "data"

Expand Down
53 changes: 53 additions & 0 deletions pkg/repository/mirror.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,21 @@ func (l *localFilesystem) Grant(id, name string, key *v1manifest.KeyInfo) error
return nil
}

// Rotate implements the model.Backend interface
func (l *localFilesystem) Rotate(m *v1manifest.Manifest) error {
txn, err := store.New(l.rootPath, l.upstream).Begin()
if err != nil {
return err
}

if err := model.New(txn, l.keys).Rotate(m); err != nil {
_ = txn.Rollback()
return err
}

return nil
}

// Download implements the Mirror interface
func (l *localFilesystem) Download(resource, targetDir string) error {
reader, err := l.Fetch(resource, 0)
Expand Down Expand Up @@ -341,6 +356,39 @@ func (l *httpMirror) Grant(id, name string, key *v1manifest.KeyInfo) error {
return errors.Errorf("introduce a fresher via the internet is not allowd, please set you mirror to a local one")
}

// Rotate implements the model.Backend interface
func (l *httpMirror) Rotate(m *v1manifest.Manifest) error {
rotateAddr := fmt.Sprintf("%s/api/v1/rotate", l.Source())
data, err := json.Marshal(m)
if err != nil {
return errors.Annotate(err, "marshal root manfiest")
}

client := http.Client{Timeout: time.Minute}
resp, err := client.Post(rotateAddr, "text/json", bytes.NewBuffer(data))
if err != nil {
return err
}
defer resp.Body.Close()

if resp.StatusCode < 300 {
return nil
}
switch resp.StatusCode {
case http.StatusConflict:
return errors.Errorf("The manifest has been modified after you fetch it, please try again")
case http.StatusBadRequest:
return errors.Errorf("The server rejected the manifest, please check if it's a valid root manifest")
default:
buf := new(strings.Builder)
if _, err := io.Copy(buf, resp.Body); err != nil {
return err
}

return fmt.Errorf("Unknow error from server, response code: %d response body: %s", resp.StatusCode, buf.String())
}
}

// Publish implements the model.Backend interface
func (l *httpMirror) Publish(manifest *v1manifest.Manifest, info model.ComponentInfo) error {
sid := uuid.New().String()
Expand Down Expand Up @@ -506,6 +554,11 @@ func (l *MockMirror) Grant(id, name string, key *v1manifest.KeyInfo) error {
return nil
}

// Rotate implements the model.Backend interface
func (l *MockMirror) Rotate(m *v1manifest.Manifest) error {
return nil
}

// Publish implements the Mirror interface
func (l *MockMirror) Publish(manifest *v1manifest.Manifest, info model.ComponentInfo) error {
// Mock point for unit test
Expand Down
4 changes: 4 additions & 0 deletions pkg/repository/model/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@ var (
ErrorMissingOwner = errors.New("owner not found")
// ErrorWrongSignature indicates that the signature is not correct
ErrorWrongSignature = errors.New("the signature is not correct")
// ErrorWrongManifestType indicates that the manifest type is not expected
ErrorWrongManifestType = errors.New("the manifest type is not expected")
// ErrorWrongManifestVersion indicates that the manifest version is not expected
ErrorWrongManifestVersion = errors.New("the manifest version is not expected")
)
Loading