diff --git a/cmd/osbuild-composer/config.go b/cmd/osbuild-composer/config.go index 654093ceb..2318dfb73 100644 --- a/cmd/osbuild-composer/config.go +++ b/cmd/osbuild-composer/config.go @@ -18,6 +18,9 @@ type ComposerConfigFile struct { LogLevel string `toml:"log_level"` LogFormat string `toml:"log_format"` DNFJson string `toml:"dnf-json"` + SplunkHost string `env:"SPLUNK_HEC_HOST"` + SplunkPort string `env:"SPLUNK_HEC_PORT"` + SplunkToken string `env:"SPLUNK_HEC_TOKEN"` } type KojiAPIConfig struct { diff --git a/cmd/osbuild-composer/main.go b/cmd/osbuild-composer/main.go index 0af8b71d6..bc2379135 100644 --- a/cmd/osbuild-composer/main.go +++ b/cmd/osbuild-composer/main.go @@ -1,10 +1,12 @@ package main import ( + "context" "flag" "os" "github.com/coreos/go-systemd/activation" + slogger "github.com/osbuild/osbuild-composer/pkg/splunk_logger" "github.com/sirupsen/logrus" "github.com/sirupsen/logrus/hooks/syslog" ) @@ -60,6 +62,15 @@ func main() { logrus.Fatalf("Error printing configuration: %v", err) } + if config.SplunkHost != "" { + hook, err := slogger.NewSplunkHook(context.Background(), config.SplunkHost, config.SplunkPort, config.SplunkToken, "composer") + + if err != nil { + panic(err) + } + logrus.AddHook(hook) + } + stateDir, ok := os.LookupEnv("STATE_DIRECTORY") if !ok { logrus.Fatal("STATE_DIRECTORY is not set. Is the service file missing StateDirectory=?") diff --git a/go.mod b/go.mod index 3ce6bd767..582868d8c 100644 --- a/go.mod +++ b/go.mod @@ -32,6 +32,7 @@ require ( github.com/openshift-online/ocm-sdk-go v0.1.385 github.com/oracle/oci-go-sdk/v54 v54.0.0 github.com/osbuild/images v0.18.0 + github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20231117174845-e969a9dc3cd1 github.com/osbuild/pulp-client v0.1.0 github.com/prometheus/client_golang v1.17.0 github.com/segmentio/ksuid v1.0.4 diff --git a/go.sum b/go.sum index 2e39fff07..59d63f6c8 100644 --- a/go.sum +++ b/go.sum @@ -455,6 +455,8 @@ github.com/oracle/oci-go-sdk/v54 v54.0.0 h1:CDLjeSejv2aDpElAJrhKpi6zvT/zhZCZuXch github.com/oracle/oci-go-sdk/v54 v54.0.0/go.mod h1:+t+yvcFGVp+3ZnztnyxqXfQDsMlq8U25faBLa+mqCMc= github.com/osbuild/images v0.18.0 h1:I/tOO7DCECciJptrXVq+oykJI5dP1rwkzJqmf2rKuqw= github.com/osbuild/images v0.18.0/go.mod h1:Zr+AkaX/Rpxyff6Zxh8kkwGKFtJsSukGo1Vv/j9HsxA= +github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20231117174845-e969a9dc3cd1 h1:UFEJIcPa46W8gtWgOYzriRKYyy1t6SWL0BI7fPTuVvc= +github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20231117174845-e969a9dc3cd1/go.mod h1:z+WA+dX6qMwc7fqY5jCzESDIlg4WR2sBQezxsoXv9Ik= github.com/osbuild/pulp-client v0.1.0 h1:L0C4ezBJGTamN3BKdv+rKLuq/WxXJbsFwz/Hj7aEmJ8= github.com/osbuild/pulp-client v0.1.0/go.mod h1:rd/MLdfwwO2cQI1s056h8z32zAi3Bo90XhlAAryIvWc= github.com/otiai10/copy v1.14.0 h1:dCI/t1iTdYGtkvCuBG2BgR6KZa83PTclw4U5n2wAllU= diff --git a/templates/composer.yml b/templates/composer.yml index f58441e36..6b931c808 100644 --- a/templates/composer.yml +++ b/templates/composer.yml @@ -103,8 +103,21 @@ objects: value: "${PGSSLMODE}" - name: PGMAXCONNS value: "${PGMAXCONNS}" - - name: SYSLOG_SERVER - value: "localhost:5140" + # Splunk forwarding + - name: SPLUNK_HEC_TOKEN + valueFrom: + secretKeyRef: + name: splunk + key: token + optional: true + - name: SPLUNK_HEC_HOST + valueFrom: + secretKeyRef: + name: splunk + key: url + optional: true + - name: SPLUNK_HEC_PORT + value: "${SPLUNK_HEC_PORT}" ports: - name: composer-api protocol: TCP @@ -123,32 +136,6 @@ objects: mountPath: "/var/lib/osbuild-composer" - name: cache-directory mountPath: "/var/cache/osbuild-composer" - - image: "quay.io/app-sre/fluentd-hec:1.2.13" - name: fluentd-sidecar - resources: - requests: - cpu: "${FLUENTD_CPU_REQUEST}" - memory: "${MEMORY_REQUEST}" - limits: - cpu: "${FLUENTD_CPU_LIMIT}" - memory: "${MEMORY_LIMIT}" - env: - - name: SPLUNK_HEC_TOKEN - valueFrom: - secretKeyRef: - name: splunk - key: token - optional: false - - name: SPLUNK_HEC_URL - valueFrom: - secretKeyRef: - name: splunk - key: url - optional: false - volumeMounts: - - name: fluentd-config - mountPath: /fluentd/etc - readOnly: true volumes: - name: composer-config configMap: @@ -157,9 +144,6 @@ objects: emptyDir: {} - name: cache-directory emptyDir: {} - - name: fluentd-config - configMap: - name: fluentd-config initContainers: - name: composer-migrate image: "${IMAGE_NAME}:${IMAGE_TAG}" @@ -273,30 +257,6 @@ objects: jwt_keys_urls = ["${RH_SSO_BASE_URL}/protocol/openid-connect/certs"] jwt_acl_file = "${COMPOSER_CONFIG_DIR}/acl.yml" jwt_tenant_provider_fields = ["rh-org-id", "account_id"] -- apiVersion: v1 - kind: ConfigMap - metadata: - name: fluentd-config - data: - fluent.conf: | - - @type syslog - port 5140 - bind 127.0.0.1 - - - tag osbuild-composer - - time_format %Y-%m-%dT%H:%M:%SZ - - - - - @type splunk_hec - hec_host "#{ENV['SPLUNK_HEC_URL']}" - hec_port "${SPLUNK_HEC_PORT}" - hec_token "#{ENV['SPLUNK_HEC_TOKEN']}" - - apiVersion: batch/v1 kind: CronJob metadata: @@ -523,7 +483,6 @@ parameters: name: MAINTENANCE_MAX_CONCURRENT_REQUESTS value: "10" required: true - - description: fluentd-hec splunk port + - description: Splunk HTTP Event Collector port name: SPLUNK_HEC_PORT value: "443" - required: true diff --git a/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/LICENSE b/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor 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, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/splunk_hook.go b/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/splunk_hook.go new file mode 100644 index 000000000..4c0f0702a --- /dev/null +++ b/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/splunk_hook.go @@ -0,0 +1,45 @@ +package logger + +import ( + "context" + "fmt" + "os" + + "github.com/sirupsen/logrus" +) + +type SplunkHook struct { + sl *SplunkLogger +} + +func NewSplunkHook(context context.Context, host, port, token, source string) (*SplunkHook, error) { + url := fmt.Sprintf("https://%s:%s/services/collector/event", host, port) + hostname, err := os.Hostname() + if err != nil { + return nil, err + } + + return &SplunkHook{ + sl: NewSplunkLogger(context, url, token, source, hostname), + }, nil +} + +func (sh *SplunkHook) Fire(entry *logrus.Entry) error { + msg, err := entry.String() + if err != nil { + return err + } + + return sh.sl.LogWithTime(entry.Time, msg) +} + +func (sh *SplunkHook) Levels() []logrus.Level { + return []logrus.Level{ + logrus.PanicLevel, + logrus.FatalLevel, + logrus.ErrorLevel, + logrus.WarnLevel, + logrus.InfoLevel, + logrus.DebugLevel, + } +} diff --git a/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/splunk_logger.go b/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/splunk_logger.go new file mode 100644 index 000000000..1ec68a4e1 --- /dev/null +++ b/vendor/github.com/osbuild/osbuild-composer/pkg/splunk_logger/splunk_logger.go @@ -0,0 +1,154 @@ +package logger + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "os" + "time" + + "github.com/hashicorp/go-retryablehttp" +) + +const ( + PayloadsChannelSize = 1000 + // in seconds, how often batched events should be sent + SendFrequency = 5 +) + +type SplunkLogger struct { + client *http.Client + url string + token string + source string + hostname string + + payloads chan *SplunkPayload +} + +type SplunkPayload struct { + // splunk expects unix time in seconds + Time int64 `json:"time"` + Host string `json:"host"` + Event SplunkEvent `json:"event"` +} + +type SplunkEvent struct { + Message string `json:"message"` + Ident string `json:"ident"` + Host string `json:"host"` +} + +func NewSplunkLogger(context context.Context, url, token, source, hostname string) *SplunkLogger { + sl := &SplunkLogger{ + client: retryablehttp.NewClient().StandardClient(), + url: url, + token: token, + source: source, + hostname: hostname, + } + + ticker := time.NewTicker(time.Second * SendFrequency) + sl.payloads = make(chan *SplunkPayload, PayloadsChannelSize) + + go sl.flushPayloads(context, ticker.C) + + return sl +} + +func (sl *SplunkLogger) flushPayloads(context context.Context, ticker <-chan time.Time) { + var payloads []*SplunkPayload + for { + select { + case <-context.Done(): + err := sl.SendPayloads(payloads) + if err != nil { + fmt.Fprintf(os.Stderr, "Splunk logger unable to send payloads: %v", err) + } + return + case p := <-sl.payloads: + if p != nil { + payloads = append(payloads, p) + } + if len(payloads) == PayloadsChannelSize { + err := sl.SendPayloads(payloads) + if err != nil { + fmt.Fprintf(os.Stderr, "Splunk logger unable to send payloads: %v", err) + } + payloads = nil + } + case <-ticker: + err := sl.SendPayloads(payloads) + if err != nil { + fmt.Fprintf(os.Stderr, "Splunk logger unable to send payloads: %v", err) + } + payloads = nil + } + } +} + +func (sl *SplunkLogger) SendPayloads(payloads []*SplunkPayload) error { + if len(payloads) == 0 { + return nil + } + + buf := bytes.NewBuffer(nil) + for _, pl := range payloads { + b, err := json.Marshal(pl) + if err != nil { + return err + } + + _, err = buf.Write(b) + if err != nil { + return err + } + } + + req, err := http.NewRequest("POST", sl.url, bytes.NewReader(buf.Bytes())) + if err != nil { + return err + } + req.Header.Add("Content-Type", "application/json") + req.Header.Add("Authorization", fmt.Sprintf("Splunk %s", sl.token)) + + res, err := sl.client.Do(req) + if err != nil { + return err + } + defer func() { + if err := res.Body.Close(); err != nil { + fmt.Fprintf(os.Stderr, "Unable to close response body when sending payloads") + } + }() + + if res.StatusCode != http.StatusOK { + buf := bytes.Buffer{} + _, err = buf.ReadFrom(res.Body) + if err != nil { + return fmt.Errorf("Error forwarding to splunk: parsing response failed: %v", err) + } + return fmt.Errorf("Error forwarding to splunk: %s", buf.String()) + } + return nil +} + +func (sl *SplunkLogger) LogWithTime(t time.Time, msg string) error { + sp := SplunkPayload{ + Time: t.Unix(), + Host: sl.hostname, + Event: SplunkEvent{ + Message: msg, + Ident: sl.source, + Host: sl.hostname, + }, + } + select { + case sl.payloads <- &sp: + default: + return fmt.Errorf("Error queueing splunk payload, channel full") + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 53de6e8a4..36c8babec 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -691,6 +691,9 @@ github.com/osbuild/images/pkg/rhsm/facts github.com/osbuild/images/pkg/rpmmd github.com/osbuild/images/pkg/runner github.com/osbuild/images/pkg/subscription +# github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20231117174845-e969a9dc3cd1 +## explicit; go 1.19 +github.com/osbuild/osbuild-composer/pkg/splunk_logger # github.com/osbuild/pulp-client v0.1.0 ## explicit; go 1.19 github.com/osbuild/pulp-client/pulpclient