github.com/pwn-term/docker@v0.0.0-20210616085119-6e977cce2565/moby/api/server/router/plugin/plugin_routes.go (about) 1 package plugin // import "github.com/docker/docker/api/server/router/plugin" 2 3 import ( 4 "context" 5 "encoding/base64" 6 "encoding/json" 7 "io" 8 "net/http" 9 "strconv" 10 "strings" 11 12 "github.com/docker/distribution/reference" 13 "github.com/docker/docker/api/server/httputils" 14 "github.com/docker/docker/api/types" 15 "github.com/docker/docker/api/types/filters" 16 "github.com/docker/docker/errdefs" 17 "github.com/docker/docker/pkg/ioutils" 18 "github.com/docker/docker/pkg/streamformatter" 19 "github.com/pkg/errors" 20 ) 21 22 func parseHeaders(headers http.Header) (map[string][]string, *types.AuthConfig) { 23 24 metaHeaders := map[string][]string{} 25 for k, v := range headers { 26 if strings.HasPrefix(k, "X-Meta-") { 27 metaHeaders[k] = v 28 } 29 } 30 31 // Get X-Registry-Auth 32 authEncoded := headers.Get("X-Registry-Auth") 33 authConfig := &types.AuthConfig{} 34 if authEncoded != "" { 35 authJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) 36 if err := json.NewDecoder(authJSON).Decode(authConfig); err != nil { 37 authConfig = &types.AuthConfig{} 38 } 39 } 40 41 return metaHeaders, authConfig 42 } 43 44 // parseRemoteRef parses the remote reference into a reference.Named 45 // returning the tag associated with the reference. In the case the 46 // given reference string includes both digest and tag, the returned 47 // reference will have the digest without the tag, but the tag will 48 // be returned. 49 func parseRemoteRef(remote string) (reference.Named, string, error) { 50 // Parse remote reference, supporting remotes with name and tag 51 remoteRef, err := reference.ParseNormalizedNamed(remote) 52 if err != nil { 53 return nil, "", err 54 } 55 56 type canonicalWithTag interface { 57 reference.Canonical 58 Tag() string 59 } 60 61 if canonical, ok := remoteRef.(canonicalWithTag); ok { 62 remoteRef, err = reference.WithDigest(reference.TrimNamed(remoteRef), canonical.Digest()) 63 if err != nil { 64 return nil, "", err 65 } 66 return remoteRef, canonical.Tag(), nil 67 } 68 69 remoteRef = reference.TagNameOnly(remoteRef) 70 71 return remoteRef, "", nil 72 } 73 74 func (pr *pluginRouter) getPrivileges(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 75 if err := httputils.ParseForm(r); err != nil { 76 return err 77 } 78 79 metaHeaders, authConfig := parseHeaders(r.Header) 80 81 ref, _, err := parseRemoteRef(r.FormValue("remote")) 82 if err != nil { 83 return err 84 } 85 86 privileges, err := pr.backend.Privileges(ctx, ref, metaHeaders, authConfig) 87 if err != nil { 88 return err 89 } 90 return httputils.WriteJSON(w, http.StatusOK, privileges) 91 } 92 93 func (pr *pluginRouter) upgradePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 94 if err := httputils.ParseForm(r); err != nil { 95 return errors.Wrap(err, "failed to parse form") 96 } 97 98 var privileges types.PluginPrivileges 99 dec := json.NewDecoder(r.Body) 100 if err := dec.Decode(&privileges); err != nil { 101 return errors.Wrap(err, "failed to parse privileges") 102 } 103 if dec.More() { 104 return errors.New("invalid privileges") 105 } 106 107 metaHeaders, authConfig := parseHeaders(r.Header) 108 ref, tag, err := parseRemoteRef(r.FormValue("remote")) 109 if err != nil { 110 return err 111 } 112 113 name, err := getName(ref, tag, vars["name"]) 114 if err != nil { 115 return err 116 } 117 w.Header().Set("Docker-Plugin-Name", name) 118 119 w.Header().Set("Content-Type", "application/json") 120 output := ioutils.NewWriteFlusher(w) 121 122 if err := pr.backend.Upgrade(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil { 123 if !output.Flushed() { 124 return err 125 } 126 _, _ = output.Write(streamformatter.FormatError(err)) 127 } 128 129 return nil 130 } 131 132 func (pr *pluginRouter) pullPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 133 if err := httputils.ParseForm(r); err != nil { 134 return errors.Wrap(err, "failed to parse form") 135 } 136 137 var privileges types.PluginPrivileges 138 dec := json.NewDecoder(r.Body) 139 if err := dec.Decode(&privileges); err != nil { 140 return errors.Wrap(err, "failed to parse privileges") 141 } 142 if dec.More() { 143 return errors.New("invalid privileges") 144 } 145 146 metaHeaders, authConfig := parseHeaders(r.Header) 147 ref, tag, err := parseRemoteRef(r.FormValue("remote")) 148 if err != nil { 149 return err 150 } 151 152 name, err := getName(ref, tag, r.FormValue("name")) 153 if err != nil { 154 return err 155 } 156 w.Header().Set("Docker-Plugin-Name", name) 157 158 w.Header().Set("Content-Type", "application/json") 159 output := ioutils.NewWriteFlusher(w) 160 161 if err := pr.backend.Pull(ctx, ref, name, metaHeaders, authConfig, privileges, output); err != nil { 162 if !output.Flushed() { 163 return err 164 } 165 _, _ = output.Write(streamformatter.FormatError(err)) 166 } 167 168 return nil 169 } 170 171 func getName(ref reference.Named, tag, name string) (string, error) { 172 if name == "" { 173 if _, ok := ref.(reference.Canonical); ok { 174 trimmed := reference.TrimNamed(ref) 175 if tag != "" { 176 nt, err := reference.WithTag(trimmed, tag) 177 if err != nil { 178 return "", err 179 } 180 name = reference.FamiliarString(nt) 181 } else { 182 name = reference.FamiliarString(reference.TagNameOnly(trimmed)) 183 } 184 } else { 185 name = reference.FamiliarString(ref) 186 } 187 } else { 188 localRef, err := reference.ParseNormalizedNamed(name) 189 if err != nil { 190 return "", err 191 } 192 if _, ok := localRef.(reference.Canonical); ok { 193 return "", errors.New("cannot use digest in plugin tag") 194 } 195 if reference.IsNameOnly(localRef) { 196 // TODO: log change in name to out stream 197 name = reference.FamiliarString(reference.TagNameOnly(localRef)) 198 } 199 } 200 return name, nil 201 } 202 203 func (pr *pluginRouter) createPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 204 if err := httputils.ParseForm(r); err != nil { 205 return err 206 } 207 208 options := &types.PluginCreateOptions{ 209 RepoName: r.FormValue("name")} 210 211 if err := pr.backend.CreateFromContext(ctx, r.Body, options); err != nil { 212 return err 213 } 214 // TODO: send progress bar 215 w.WriteHeader(http.StatusNoContent) 216 return nil 217 } 218 219 func (pr *pluginRouter) enablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 220 if err := httputils.ParseForm(r); err != nil { 221 return err 222 } 223 224 name := vars["name"] 225 timeout, err := strconv.Atoi(r.Form.Get("timeout")) 226 if err != nil { 227 return err 228 } 229 config := &types.PluginEnableConfig{Timeout: timeout} 230 231 return pr.backend.Enable(name, config) 232 } 233 234 func (pr *pluginRouter) disablePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 235 if err := httputils.ParseForm(r); err != nil { 236 return err 237 } 238 239 name := vars["name"] 240 config := &types.PluginDisableConfig{ 241 ForceDisable: httputils.BoolValue(r, "force"), 242 } 243 244 return pr.backend.Disable(name, config) 245 } 246 247 func (pr *pluginRouter) removePlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 248 if err := httputils.ParseForm(r); err != nil { 249 return err 250 } 251 252 name := vars["name"] 253 config := &types.PluginRmConfig{ 254 ForceRemove: httputils.BoolValue(r, "force"), 255 } 256 return pr.backend.Remove(name, config) 257 } 258 259 func (pr *pluginRouter) pushPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 260 if err := httputils.ParseForm(r); err != nil { 261 return errors.Wrap(err, "failed to parse form") 262 } 263 264 metaHeaders, authConfig := parseHeaders(r.Header) 265 266 w.Header().Set("Content-Type", "application/json") 267 output := ioutils.NewWriteFlusher(w) 268 269 if err := pr.backend.Push(ctx, vars["name"], metaHeaders, authConfig, output); err != nil { 270 if !output.Flushed() { 271 return err 272 } 273 _, _ = output.Write(streamformatter.FormatError(err)) 274 } 275 return nil 276 } 277 278 func (pr *pluginRouter) setPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 279 var args []string 280 if err := json.NewDecoder(r.Body).Decode(&args); err != nil { 281 if err == io.EOF { 282 return errdefs.InvalidParameter(errors.New("got EOF while reading request body")) 283 } 284 return errdefs.InvalidParameter(err) 285 } 286 if err := pr.backend.Set(vars["name"], args); err != nil { 287 return err 288 } 289 w.WriteHeader(http.StatusNoContent) 290 return nil 291 } 292 293 func (pr *pluginRouter) listPlugins(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 294 if err := httputils.ParseForm(r); err != nil { 295 return err 296 } 297 298 pluginFilters, err := filters.FromJSON(r.Form.Get("filters")) 299 if err != nil { 300 return err 301 } 302 l, err := pr.backend.List(pluginFilters) 303 if err != nil { 304 return err 305 } 306 return httputils.WriteJSON(w, http.StatusOK, l) 307 } 308 309 func (pr *pluginRouter) inspectPlugin(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { 310 result, err := pr.backend.Inspect(vars["name"]) 311 if err != nil { 312 return err 313 } 314 return httputils.WriteJSON(w, http.StatusOK, result) 315 }