| 1 | //go:build linux |
| 2 | |
| 3 | package operator |
| 4 | |
| 5 | import ( |
| 6 | "bytes" |
| 7 | "context" |
| 8 | "database/sql" |
| 9 | "errors" |
| 10 | "golang.org/x/sys/unix" |
| 11 | "os" |
| 12 | "time" |
| 13 | ) |
| 14 | |
| 15 | type CommandExecutor interface { |
| 16 | Execute(context.Context, []byte) ([]byte, error) |
| 17 | } |
| 18 | type CommandMaterial struct { |
| 19 | Role, Origin string |
| 20 | Sealer Sealer |
| 21 | MAC []byte |
| 22 | OtherKeys [][]byte |
| 23 | CheckSchema func(context.Context, *sql.Conn) error |
| 24 | Build func(*Engine) (CommandExecutor, error) |
| 25 | } |
| 26 | |
| 27 | // RunCommand verifies configuration, private launch transport and signature |
| 28 | // before exposing the bound database to the role executor. |
| 29 | func RunCommand(ctx context.Context, database string, material CommandMaterial) (result *CommandResult, err error) { |
| 30 | var out []byte |
| 31 | if ctx == nil || material.CheckSchema == nil || material.Build == nil || material.Sealer == nil || len(material.MAC) != 32 || !commandOutputPipe() { |
| 32 | return nil, ErrRefused |
| 33 | } |
| 34 | cfg, verifier, err := parseCommandConfig(os.Getenv(commandConfigEnvironment)) |
| 35 | if err != nil || material.Role != cfg.Role || material.Origin != cfg.Origin { |
| 36 | return nil, ErrRefused |
| 37 | } |
| 38 | for _, key := range material.OtherKeys { |
| 39 | if bytes.Equal(key, verifier.key) { |
| 40 | return nil, ErrRefused |
| 41 | } |
| 42 | } |
| 43 | invocation, err := openConfiguredInvocation(ctx, database, cfg, verifier) |
| 44 | if err != nil { |
| 45 | return nil, err |
| 46 | } |
| 47 | defer func() { |
| 48 | if closeErr := invocation.close(); closeErr != nil { |
| 49 | err = ErrUnavailable |
| 50 | } |
| 51 | if err != nil { |
| 52 | clear(out) |
| 53 | out = nil |
| 54 | result = nil |
| 55 | } |
| 56 | }() |
| 57 | engine, err := NewEngine(invocation.database.db, verifier, material.Sealer, material.MAC, time.Now) |
| 58 | if err != nil { |
| 59 | return nil, err |
| 60 | } |
| 61 | engine.checkSchema = material.CheckSchema |
| 62 | executor, err := material.Build(engine) |
| 63 | if err != nil || executor == nil { |
| 64 | return nil, ErrRefused |
| 65 | } |
| 66 | if invocation.inspect { |
| 67 | directory, ok := executor.(*Directory) |
| 68 | if !ok { |
| 69 | return nil, ErrRefused |
| 70 | } |
| 71 | out, err = directory.Inspect(invocation.ctx, invocation.raw) |
| 72 | } else { |
| 73 | out, err = executor.Execute(invocation.ctx, invocation.raw) |
| 74 | } |
| 75 | if err != nil { |
| 76 | return nil, err |
| 77 | } |
| 78 | if len(out) == 0 || len(out) > InputLimit { |
| 79 | return nil, ErrUnavailable |
| 80 | } |
| 81 | deadline, _ := invocation.ctx.Deadline() |
| 82 | return &CommandResult{out, deadline}, nil |
| 83 | } |
| 84 | |
| 85 | type CommandResult struct { |
| 86 | raw []byte |
| 87 | deadline time.Time |
| 88 | } |
| 89 | |
| 90 | func (r *CommandResult) Write(ctx context.Context) error { |
| 91 | if r == nil { |
| 92 | return ErrRefused |
| 93 | } |
| 94 | defer func() { clear(r.raw); r.raw = nil }() |
| 95 | if ctx == nil || len(r.raw) == 0 || !time.Now().Before(r.deadline) { |
| 96 | return ErrUnavailable |
| 97 | } |
| 98 | if !commandOutputPipe() { |
| 99 | return ErrUnavailable |
| 100 | } |
| 101 | if unix.SetNonblock(1, true) != nil { |
| 102 | return ErrUnavailable |
| 103 | } |
| 104 | written := 0 |
| 105 | for written < len(r.raw) { |
| 106 | if ctx.Err() != nil || !time.Now().Before(r.deadline) { |
| 107 | return ErrUnavailable |
| 108 | } |
| 109 | n, err := unix.Write(1, r.raw[written:]) |
| 110 | if n > 0 { |
| 111 | written += n |
| 112 | } |
| 113 | if errors.Is(err, unix.EINTR) { |
| 114 | continue |
| 115 | } |
| 116 | if errors.Is(err, unix.EAGAIN) { |
| 117 | timer := time.NewTimer(time.Millisecond) |
| 118 | select { |
| 119 | case <-ctx.Done(): |
| 120 | timer.Stop() |
| 121 | return ErrUnavailable |
| 122 | case <-timer.C: |
| 123 | } |
| 124 | continue |
| 125 | } |
| 126 | if err != nil || n == 0 { |
| 127 | return ErrUnavailable |
| 128 | } |
| 129 | } |
| 130 | if unix.Close(1) != nil || ctx.Err() != nil || !time.Now().Before(r.deadline) { |
| 131 | return ErrUnavailable |
| 132 | } |
| 133 | return nil |
| 134 | } |
| 135 | |
| 136 | func commandOutputPipe() bool { |
| 137 | var st unix.Stat_t |
| 138 | flags, err := unix.FcntlInt(1, unix.F_GETFL, 0) |
| 139 | return err == nil && flags&unix.O_ACCMODE == unix.O_WRONLY && unix.Fstat(1, &st) == nil && st.Mode&unix.S_IFMT == unix.S_IFIFO |
| 140 | } |
| 141 | |