All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graylog2.rest.resources.system.GrokResource Maven / Gradle / Ivy

There is a newer version: 6.1.4
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.rest.resources.system;

import com.codahale.metrics.annotation.Timed;
import com.google.common.collect.ImmutableMap;
import io.krakens.grok.api.exception.GrokException;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.graylog2.audit.AuditEventTypes;
import org.graylog2.audit.jersey.AuditEvent;
import org.graylog2.audit.jersey.NoAuditEvent;
import org.graylog2.database.NotFoundException;
import org.graylog2.database.PaginatedList;
import org.graylog2.grok.GrokPattern;
import org.graylog2.grok.GrokPatternService;
import org.graylog2.grok.GrokPatternService.ImportStrategy;
import org.graylog2.grok.PaginatedGrokPatternService;
import org.graylog2.plugin.database.ValidationException;
import org.graylog2.rest.models.PaginatedResponse;
import org.graylog2.rest.models.system.grokpattern.requests.GrokPatternTestRequest;
import org.graylog2.rest.models.system.responses.GrokPatternList;
import org.graylog2.search.SearchQuery;
import org.graylog2.search.SearchQueryField;
import org.graylog2.search.SearchQueryParser;
import org.graylog2.shared.rest.resources.RestResource;
import org.graylog2.shared.security.RestPermissions;

import javax.inject.Inject;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static org.graylog2.shared.rest.documentation.generator.Generator.CLOUD_VISIBLE;

@RequiresAuthentication
@Path("/system/grok")
@Produces("application/json")
@Consumes("application/json")
@Api(value = "System/Grok", description = "Manage grok patterns", tags = {CLOUD_VISIBLE})
public class GrokResource extends RestResource {
    private static final Pattern GROK_LINE_PATTERN = Pattern.compile("^(\\w+)[ \t]+(.*)$");

    private static final ImmutableMap SEARCH_FIELD_MAPPING = ImmutableMap.builder()
            .put(GrokPattern.FIELD_NAME, SearchQueryField.create(GrokPattern.FIELD_NAME))
            .put(GrokPattern.FIELD_PATTERN, SearchQueryField.create(GrokPattern.FIELD_PATTERN))
            .build();

    private final GrokPatternService grokPatternService;
    private final SearchQueryParser searchQueryParser;
    private final PaginatedGrokPatternService paginatedGrokPatternService;

    @Inject
    public GrokResource(GrokPatternService grokPatternService,
                        PaginatedGrokPatternService paginatedGrokPatternService) {

        this.paginatedGrokPatternService = paginatedGrokPatternService;
        this.grokPatternService = grokPatternService;
        this.searchQueryParser = new SearchQueryParser(GrokPattern.FIELD_NAME, SEARCH_FIELD_MAPPING);
    }

    @GET
    @Timed
    @Deprecated
    @ApiOperation("Get all existing grok patterns")
    public GrokPatternList listGrokPatterns() {
        checkPermission(RestPermissions.INPUTS_READ);

        return GrokPatternList.create(grokPatternService.loadAll());
    }

    @GET
    @Timed
    @Path("/paginated")
    @ApiOperation("Get existing grok patterns paged")
    @Produces(MediaType.APPLICATION_JSON)
    public PaginatedResponse getPage(@ApiParam(name = "page") @QueryParam("page") @DefaultValue("1") int page,
                                     @ApiParam(name = "per_page") @QueryParam("per_page") @DefaultValue("50") int perPage,
                                     @ApiParam(name = "query") @QueryParam("query") @DefaultValue("") String query,
                                     @ApiParam(name = "sort",
                                               value = "The field to sort the result on",
                                               required = true,
                                               allowableValues = "title,description,id")
                                       @DefaultValue(GrokPattern.FIELD_NAME) @QueryParam("sort") String sort,
                                     @ApiParam(name = "order", value = "The sort direction", allowableValues = "asc, desc")
                                       @DefaultValue("asc") @QueryParam("order") String order) {
        checkPermission(RestPermissions.INPUTS_READ);

        SearchQuery searchQuery;
        try {
            searchQuery = searchQueryParser.parse(query);
        } catch (IllegalArgumentException e) {
            throw new BadRequestException("Invalid argument in search query: " + e.getMessage());
        }
        final PaginatedList result = paginatedGrokPatternService
                .findPaginated(searchQuery, page, perPage, sort, order);

        return PaginatedResponse.create("patterns", result);
    }

    @GET
    @Timed
    @Path("/{patternId}")
    @ApiOperation("Get the existing grok pattern")
    public GrokPattern listPattern(@ApiParam(name = "patternId", required = true)
                                       @PathParam("patternId") String patternId) throws NotFoundException {
        checkPermission(RestPermissions.INPUTS_READ);

        return grokPatternService.load(patternId);
    }

    @POST
    @Timed
    @Path("/test")
    @NoAuditEvent("Only used to test pattern.")
    @ApiOperation(value = "Test pattern with sample data")
    public Response testPattern(@ApiParam(name = "pattern", required = true) GrokPatternTestRequest request) {
        Map result;
        try {
            result = grokPatternService.match(request.grokPattern(), request.sampleData());
        } catch (GrokException | IllegalArgumentException e) {
            Map error = ImmutableMap.of("message", e.getMessage());

            throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST).entity(error).build());
        }
        return Response.ok(result).build();
    }

    @POST
    @Timed
    @ApiOperation(value = "Add a new named pattern", response = GrokPattern.class)
    @AuditEvent(type = AuditEventTypes.GROK_PATTERN_CREATE)
    public Response createPattern(@ApiParam(name = "pattern", required = true)
                                      @Valid @NotNull GrokPattern pattern) throws ValidationException {
        checkPermission(RestPermissions.INPUTS_CREATE);

        // remove the ID from the pattern, this is only used to create new patterns
        final GrokPattern newPattern = grokPatternService.save(pattern.toBuilder().id(null).build());

        final URI patternUri = getUriBuilderToSelf().path(GrokResource.class, "listPattern").build(newPattern.id());

        return Response.created(patternUri).entity(newPattern).build();
    }

    @PUT
    @Timed
    @ApiOperation("Add a list of new patterns")
    @AuditEvent(type = AuditEventTypes.GROK_PATTERN_IMPORT_CREATE)
    public Response bulkUpdatePatterns(@ApiParam(name = "patterns", required = true) @NotNull
                                               GrokPatternList patternList,
                                       // deprecated. used to drop all existing patterns before import
                                       @Deprecated @QueryParam("replace") @DefaultValue("false")
                                               boolean deprecatedDropAllExisting,
                                       @ApiParam(name = "import-strategy", value = "Strategy to apply when importing.")
                                       @QueryParam("import-strategy")
                                               ImportStrategy importStrategy) throws ValidationException {
        checkPermission(RestPermissions.INPUTS_CREATE);

        try {
            if (!grokPatternService.validateAll(patternList.patterns())) {
                throw new ValidationException("Invalid pattern contained. Did not save any patterns.");
            }
        } catch (GrokException | IllegalArgumentException e) {
            throw new ValidationException("Invalid pattern. Did not save any patterns\n" + e.getMessage());
        }

        ImportStrategy resolvedStrategy = importStrategy != null ? importStrategy :
                deprecatedDropAllExisting ? ImportStrategy.DROP_ALL_EXISTING : ImportStrategy.ABORT_ON_CONFLICT;

        grokPatternService.saveAll(patternList.patterns(), resolvedStrategy);
        return Response.accepted().build();
    }

    @POST
    @Consumes(MediaType.TEXT_PLAIN)
    @Timed
    @ApiOperation("Add a list of new patterns")
    @AuditEvent(type = AuditEventTypes.GROK_PATTERN_IMPORT_CREATE)
    public Response bulkUpdatePatternsFromTextFile(@ApiParam(name = "patterns", required = true) @NotNull InputStream patternsFile,
                                                   // deprecated. used to drop all existing patterns before import
                                                   @Deprecated @QueryParam("replace") @DefaultValue("false")
                                                           boolean deprecatedDropAllExisting,
                                                   @ApiParam(name = "import-strategy", value = "Strategy to apply when importing.")
                                                   @QueryParam("import-strategy")
                                                           ImportStrategy importStrategy) throws ValidationException, IOException {
        checkPermission(RestPermissions.INPUTS_CREATE);

        final List grokPatterns = readGrokPatterns(patternsFile);

        if (!grokPatterns.isEmpty()) {
            try {
                if (!grokPatternService.validateAll(grokPatterns)) {
                    throw new ValidationException("Invalid pattern contained. Did not save any patterns.");
                }
            } catch (GrokException | IllegalArgumentException e) {
                throw new ValidationException("Invalid pattern. Did not save any patterns\n" + e.getMessage());
            }

            ImportStrategy resolvedStrategy = importStrategy != null ? importStrategy :
                    deprecatedDropAllExisting ? ImportStrategy.DROP_ALL_EXISTING : ImportStrategy.ABORT_ON_CONFLICT;

            grokPatternService.saveAll(grokPatterns, resolvedStrategy);
        }

        return Response.accepted().build();
    }

    private List readGrokPatterns(InputStream patternList) throws IOException {
        try (final InputStreamReader inputStreamReader = new InputStreamReader(patternList, StandardCharsets.UTF_8);
             final BufferedReader bufferedReader = new BufferedReader(inputStreamReader)) {
            return bufferedReader.lines()
                    .map(String::trim)
                    .filter(s -> !s.startsWith("#") && !s.isEmpty())
                    .map(GROK_LINE_PATTERN::matcher)
                    .filter(Matcher::matches)
                    .map(matcher -> GrokPattern.create(matcher.group(1), matcher.group(2)))
                    .collect(Collectors.toList());

        }
    }

    @PUT
    @Timed
    @Path("/{patternId}")
    @ApiOperation("Update an existing pattern")
    @AuditEvent(type = AuditEventTypes.GROK_PATTERN_UPDATE)
    public GrokPattern updatePattern(@ApiParam(name = "patternId", required = true)
                                     @PathParam("patternId") String patternId,
                                     @ApiParam(name = "pattern", required = true)
                                     GrokPattern pattern) throws ValidationException {
        checkPermission(RestPermissions.INPUTS_EDIT);

        final GrokPattern grokPattern = GrokPattern.builder()
                .id(patternId)
                .name(pattern.name())
                .pattern(pattern.pattern())
                .build();

        return grokPatternService.update(grokPattern);
    }

    @DELETE
    @Timed
    @Path("/{patternId}")
    @ApiOperation("Remove an existing pattern by id")
    @AuditEvent(type = AuditEventTypes.GROK_PATTERN_DELETE)
    public void removePattern(@ApiParam(name = "patternId", required = true) @PathParam("patternId") String patternId) throws NotFoundException {
        checkPermission(RestPermissions.INPUTS_EDIT);

        grokPatternService.load(patternId);
        if (grokPatternService.delete(patternId) == 0) {
            throw new javax.ws.rs.NotFoundException("Couldn't remove Grok pattern with ID " + patternId);
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy